Java中的数据存储技术
JDBC介绍
JDBC提供了接口也就是规范,具体实现细节由具体公司提供驱动。
为什么要定义规范,如果没有规范,就添加数据命令而言,Mysql可能定义为add,而Oracle定义为insert,SQLServer定义为put,还有可能每个方法传入的参数个数不同,操作的步骤数不同,就导致用Java操作不同数据库时,代码通用性低。
SUN公司提供这套规范,让各个公司去实现实现类,这些实现类的集合就是JDBC驱动。
JDBC体系结构
JDBC编写步骤
Java.sql包在jdk中有。
驱动包手动需要导入,根据不同数据库系统导入不同的包。
创建Connection。就像操作数据库客户端时也要先连接数据库,这里不用客户端操作数据库也要连接。
Statement去帮助执行SQL。有查询和增删改。
ResultSet,当执行的是查询就需要有结果集对象了。
ODBC是微软提供的操作不同数据库的API。先连接JDBC再连接ODBC再注册驱动的一种方式。
URL
URL就是定位到某一个数据库。
在网上定位一个具体资源时的URL格式:http://localhost:8080/具体资源。
这里jdbc:mysql://就相当于http://。
获取数据库连接因为JDBC是用来定义规范的,所以JDBC的API中大部分都是接口。
获取连接的四种方式 方式1:
public class JDBC_Demo1 {
public static void main(String[] args) throws SQLException {
//获取驱动,也就是获取数据库系统厂商提供的jar包。
Driver driver = new com.mysql.cj.jdbc.Driver();
//定位到数据库
String url = "jdbc:mysql://localhost:3306/demo";
//将用户名和密码封装在Properties中
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","niit");
Connection connection = driver.connect(url,info);
System.out.println(connection);
}
}
方式2:
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
//方式2是对方式1的迭代
//为了让程序有更好的可移植性,要减少第三方程序的出现,
//比如new com.mysql.cj.jdbc.Driver()就是第三方
//1.获取Driver实现类对象,使用反射
//反射机制指的是程序在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2.提供要连接的数据库
String url = "jdbc:mysql://localhost:3306/demo";
//3.提供连接需要的用户名和密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","niit");
//4.获取连接
Connection connection = driver.connect(url,info);
System.out.println(connection);
}
}
方式3:
public class JDBC_Demo3 {
//方式3:使用DriverManager替换Driver
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
//获取Deriver实现类对象
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//注册驱动
DriverManager.registerDriver(driver);
String url = "jdbc:mysql://localhost:3306/demo";
String user = "root";
String password = "niit";
//获取连接
DriverManager.getConnection(url,user,password);
}
}
方式4:可以只是加载驱动,不用显式的注册驱动了
方式5:直接连接public class JDBC_Demo4 { @Test public void testConnection() throws ClassNotFoundException, SQLException { //方式3的优化 //1.三个基本信息 String url = "jdbc:mysql://localhost:3306/demo"; String user = "root"; String password = "niit"; //2.获取Driver实现类对象 //注册驱动这个活Driver中静态代码块做了,静态代码块随着类的加载而执行, Class.forName("com.mysql.cj.jdbc.Driver"); //3.获取连接 Connection connection = DriverManager.getConnection(url,user,password); System.out.println(connection); } }为什么可以省略注册驱动?
利用反射获取Driver对象时,随着类的加载执行了静态代码块中的代码。
自动注册了驱动,注册驱动理解为具体和哪家数据库提供的类文件对接。
注册了驱动就等于准备好了操作数据库的工具,就可以连接数据库进行操作了。
方式6:最终版,基本信息暴露在代码中不安全。public class JDBC_Demo4 { @Test public void testConnection() throws ClassNotFoundException, SQLException { //方式3的优化 //1.三个基本信息 String url = "jdbc:mysql://localhost:3306/demo"; String user = "root"; String password = "niit"; //2.获取Driver实现类对象 //注册驱动这个活Driver中静态代码块做了,静态代码块随着类的加载而执行, //Class.forName("com.mysql.cj.jdbc.Driver"); //3.获取连接 Connection connection = DriverManager.getConnection(url,user,password); System.out.println(connection); } }连接时,自动获取对象,并且注册驱动。
但不推荐这样,这样适用于MySQL,但不一定适用于别的数据库。
封装连接和关闭资源public class JDBC_Demo5 { public static void main(String[] args) throws ClassNotFoundException, IOException, SQLException { //properties读取路径默认是从当前module的src下 InputStream is = JDBC_Demo5.class.getClassLoader().getResourceAsStream("connection\jdbc.properties"); Properties properties = new Properties(); properties.load(is); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driverClass = properties.getProperty("driverClass"); Class.forName(driverClass); Connection connection = DriverManager.getConnection(url,user,password); System.out.println(connection); } }将数据库连接需要的基本信息放入配置文件中,通过读取配置文件的方式获取连接。
好处:
1.实现了数据与代码的分离,实现了解耦。
解耦的好处比如想要改这些数据可以在配置文件中修改,数据只在配置文件中,则代码和数据分开后,各自的可读性都提高。如果数据嵌套在代码中,如果一个不了解这些代码的人来修改数据就会比较麻烦,而让他直接去修改配置文件就会比较简单。
2.后期项目需要配置到服务器时需要打包,如果需要修改配置文件信息,可以避免程序重新打包。如果数据在程序中如果要更改数据就要对这个Java文件重新打包。
使用PreparedStatement实现CRUD操作public class JDBCUtils { //静态方法不需要获取对象 public static Connection getConnection(){ try{ //加载配置文件 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); properties.load(is); //获取基本信息 String url = properties.getProperty("url"); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driverClass = properties.getProperty("driverClass"); //与数据库连接 Class.forName(driverClass); Connection connection = DriverManager.getConnection(url, user, password); return connection; }catch (Exception e){ e.printStackTrace(); } return null; } //关闭资源 public static void closeResource(Connection connection, Statement ps,ResultSet resultSet){ try{ if(connection!=null) connection.close(); if(ps!=null) ps.close(); if(resultSet!=null) resultSet.close(); }catch (Exception e){ e.printStackTrace(); } } }user=root password=niit url=jdbc:mysql://localhost:3306/demo driverClass=com.mysql.cj.jdbc.Driver
CRUD:Create,Retrieve,Update,Delete 增删改查
介绍操作和访问数据库
Statement操作数据表的弊端
需要拼写SQL语句,并且有SQL注入的风险。
补充:用scanner输入时,用sc.next()输入时遇到空格或换行结束,用sc.nextLine时只以换行为结束。
使用PreparedStatementPreparedStatement是Statement的子接口。
一般的PreparedStatement操作
public class TestPreparedStatement {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//加载配置文件
InputStream is = TestPreparedStatement.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
//获取基本信息
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driverClass = properties.getProperty("driverClass");
//与数据库连接
Class.forName(driverClass);
Connection connection = DriverManager.getConnection(url,user,password);
//操作数据库
//预编译sql语句,返回PreparedStatement对象
String sql = "insert into demo(userName,age) values(?,?)";//?是占位符
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//填充占位符
preparedStatement.setString(1,"小猪");
preparedStatement.setInt(2,18);//日期型用setDate
//执行sql命令
preparedStatement.execute();
//资源的关闭
//为了避免空指针异常
if(preparedStatement!=null)
preparedStatement.close();
if (connection!=null)
connection.close();
}
//增删改
//向demo表中修改数据
}
通用的增删改操作
public class Test2 { public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException { //建议表名上加一个`,着重符,在tab键上面,假如表面是关键字,就会报错,加上`就可以避免 String sql = "delete from `demo` where age = ?"; update(sql,18); } //通用增删改操作 public static void update(String sql,Object ...args){//可变形参 try{ //获取连接 Connection connection = JDBCUtils.getConnection(); //预编译sql PreparedStatement preparedStatement = connection.prepareStatement(sql); //填充占位符 //遍历可变数组,数组的内容就是传进来填写占位符的数据 for (int i = 0;i < args.length;i++){ preparedStatement.setObject(i+1,args[i]); } //执行 preparedStatement.execute(); //关闭资源 JDBCUtils.closeResource(connection,preparedStatement,null); }catch (Exception e){ e.printStackTrace(); } } }增删改的操作不需要返回结果,只需要将占位符填写就可以。所以不同的增删改操作只是填写的占位符的个数和值不同。
一般的查询操作
//查询 public class Test3 { @Test public void testQuery() throws Exception{ Connection connection = JDBCUtils.getConnection(); String sql = "select * from demo where age = ?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1,21); //执行并返回结果集 ResultSet resultSet = preparedStatement.executeQuery(); //处理结果集 //next功能:判断下面是否有元素,有的话下移,没有的话不下移,说明有两个功能,第一个是判断,第二个是移动 if(resultSet.next()){ //获取当前一行数据的各个字段值 String name = resultSet.getString(1); int age = resultSet.getInt(2); //将数据封装为一个对象输出查询结果 System.out.println(new User(name, age)); } JDBCUtils.closeResource(connection,preparedStatement,resultSet); } }next的作用:1.判断下面是否有数据返回true或false。 2.如果有数据就下移,如果没数据就不下移。
这里用到了ORM思想
Java属性类型与MySQL字段类型对应
日期型如何传值。
针对于一个表的通用查询操作
//针对某个表的通用的查询,针对于查询时需要查询的字段个数不同的通用
//不同的表需要的JavaBean属性不同,所以是针对同一个表的通用查询
//select 字段1,字段2... from table where 字段 = ?
public class Test4 {
@Test
public void test(){
//JavaBean的属性名和字段名要相同,因为要根据属性名要给该域赋值,这个属性名是从结果集的列名上获取
String sql = "select userName,age from demo where age = ?";
System.out.println(testQuery(sql, 21));
}
public User testQuery(String sql,Object ...args){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try{
//获取连接
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
//填写占位符
for(int i = 0;i < args.length;i++){
preparedStatement.setObject(i+1,args[i]);
}
//执行并返回结果集
resultSet = preparedStatement.executeQuery();
//对结果集的一条结果进行封装
//获取结果集的列数,从而决定取的时候取多少次
//获取结果集的元数据,修饰现有数据的数据
ResultSetmetaData metaData = resultSet.getmetaData();
//获取列数
int columnCount = metaData.getColumnCount();
if(resultSet.next()){
User user = new User();
//取的时候取多少次由要查询的字段的个数决定
for(int i = 0;i
为什么是针对一个表的通用查询操作?因为不同的表的字段不同,查询一个表时,除了填写占位符,前面要查询的字段也是变量。
将结果集封装到JavaBean对象中,JavaBean对象的属性名称要和字段名称相同,因为利用反射时,是根据列名在对象中查找属性的域然后赋值。
但是因为数据库和Java中取名习惯不同,如Java中的orderID,在数据库一般取为order_id,这里就需要一步转换。
先将查询的字段起别名:select order_id orderID from demo where age = ?
然后将String columnName = metaData.getColumnName(i + 1);改为String columnLabel = metaData.getColumnLabel(i + 1);
getColumnLabel是返回结果集的别名,如果没有别名则返回列名,所以推荐使用这个方法。
针对于不同表的通用查询操作-不同表返回一个结果
public class Test5 {
@Test
public void test(){
String sql = "select userName from demo where age = ?";
User user = getInstance(User.class,sql,21);
System.out.println(user);
}
//sql是查询语句,args填充占位符
//Class这个T是运行实例
// T,T是返回值类型,声明泛型方法
public T getInstance(Class clazz,String sql,Object ...args){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try{
//获取连接
connection = JDBCUtils.getConnection();
//预编译sql
preparedStatement = connection.prepareStatement(sql);
//填写占位符
for(int i = 0;i < args.length;i++){
preparedStatement.setObject(i+1,args[i]);
}
//执行并返回结果集
resultSet = preparedStatement.executeQuery();
ResultSetmetaData metaData = resultSet.getmetaData();
//获取列数
int columnCount = metaData.getColumnCount();
if(resultSet.next()){
//将传入的类创建实例
T t = clazz.newInstance();
//取的时候取多少次由要查询的字段的个数决定
for(int i = 0;i
用到了反射和泛型。前提都是需要先将JavaBean先创建好。
针对不同表的通用查询操作-返回多个结果
public class Test6 {
@Test
public void test(){
String sql = "select userName from demo where age = ?";
List list = getForList(User.class, sql, 21);
//遍历集合
list.forEach(System.out::println);
}
public List getForList(Class clazz, String sql, Object ...args){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try{
//获取连接
connection = JDBCUtils.getConnection();
//预编译sql
preparedStatement = connection.prepareStatement(sql);
//填写占位符
for(int i = 0;i < args.length;i++){
preparedStatement.setObject(i+1,args[i]);
}
//执行并返回结果集
resultSet = preparedStatement.executeQuery();
ResultSetmetaData metaData = resultSet.getmetaData();
//获取列数
int columnCount = metaData.getColumnCount();
//创建集合
List list = new ArrayList();
while (resultSet.next()){
//将传入的类创建实例
T t = clazz.newInstance();
//处理每一行数据的每一列,给每一行看作一个对象,给字段(属性)赋值
for(int i = 0;i
只在过滤条件处填充占位符。
小结PreparedStatement的优点
1.解决了sql拼串问题。
2.解决了sql注入问题。
假如用statement语句去执行这条sql命令,过滤条件会判断为or的关系
而用占位符代替之后,预编译会认为过滤条件是且的关系,之后只填充占位符的内容,如果该内容中有or也无法改变原先且的关系,这就是预编译的好处,所以防止了SQL注入。
3.PreparedStatement操作Blob数据,而Statement做不到
4.PreparedStatement可以实现更高效的批量操作。
假如要添加一万条数据,每条数据都不同,如果用Statement的话,每条数据都要校验格式等,就需要重复一万次,而用预编译的话,由于每条数据的格式都相同,只是占位符的内容不同,所以只需要一次。以后只往占位符填充内容就ok了。
操作BLOB类型字段 介绍BLOB类型1.在操作数据库时,只需要面向JDBC这套接口编程。
在用到操作数据库的包中,导入的都是接口,都没有引入第三方的API(也就是各个数据库厂商提供的jar包),那么引用这些jar包的?
通过拿着这个Driver去获得具体的连接。这个connection就是MySQL的连接。这个connection利用了多态,表面上返回了一个接口,其实这个connection是一个MySQL具体实现类的对象。表面上都是使用的接口,实际上都是具体的MySQL实现类对象。
BLOB=binary large object, 可以用BLOB类型存图片,视频等。
对BLOB数据的操作 向数据库中存BLOB数据
//向数据库中存BLOB数据
public class BlobTest1 {
@Test
public void test() throws Exception{
Connection connection = JDBCUtils.getConnection();
String sql = "insert into blobtable values (?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,1);
//这个fis就代表了这张图片
FileInputStream fis = new FileInputStream(new File("C:\Users\24213\Pictures\Camera Roll\little cat.webp"));
preparedStatement.setBlob(2,fis);
preparedStatement.execute();
JDBCUtils.closeResource(connection,preparedStatement,null);
}
}
向数据库中查找BLOB数据并将数据保存到本地
public class BlobTest2 {
//查找BLOB数据
@Test
public void test()throws Exception{
Connection connection = JDBCUtils.getConnection();
String sql = "select photo from blobtable where id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,1);
ResultSet resultSet = preparedStatement.executeQuery();
InputStream is = null;
FileOutputStream fos = null;
if(resultSet.next()){
//将Blob类型的字段数据提取出来
//可以定义成员变量存储,也可以存储到对象中
Blob photo = resultSet.getBlob("photo");
//保存到本地
is = photo.getBinaryStream();
fos = new FileOutputStream("little cat.jpg");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
}
if(is!=null)
is.close();
if(fos!=null)
fos.close();
JDBCUtils.closeResource(connection,preparedStatement,resultSet);
}
}
数据库的批量操作
使用PreparedStatement批量插入操作
update和delete本身具有批量操作的效果,如果不设置过滤条件,那么这个操作默认就是对整体进行。
优化批量插入操作批量插入操作时Statement和PreparedStatement的对比
进行批处理
public class Test1 { @Test public void test() throws Exception{ long start = System.currentTimeMillis(); Connection connection = JDBCUtils.getConnection(); String sql = "insert into goods values (?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); for (int i = 0;i < 2000;i++){ preparedStatement.setObject(1,i); preparedStatement.setString(2,"good"); //1.攒sql preparedStatement.addBatch(); //每攒500个执行命令就统一执行一次 if(i % 500 == 0){ //2.执行batch preparedStatement.executeBatch(); //3.清空batch preparedStatement.clearBatch(); } } long end = System.currentTimeMillis(); System.out.println(end-start);//5345 //409 JDBCUtils.closeResource(connection,preparedStatement,null); } }MySQL默认不支持这种操作,需要更改配置文件信息
未优化前插入两万条数据要5345毫秒,优化后409毫秒。
进一步优化
DAO及其实现类public class Test1 { @Test public void test() throws Exception{ long start = System.currentTimeMillis(); Connection connection = JDBCUtils.getConnection(); String sql = "insert into goods values (?,?)"; connection.setAutoCommit(false); PreparedStatement preparedStatement = connection.prepareStatement(sql); for (int i = 0;i < 2000;i++){ preparedStatement.setObject(1,i); preparedStatement.setString(2,"good"); //1.攒sql preparedStatement.addBatch(); if(i % 500 == 0){ //2.执行batch preparedStatement.executeBatch(); //3.清空batch preparedStatement.clearBatch(); } } connection.commit(); long end = System.currentTimeMillis(); System.out.println(end-start);//5345 //409 //443 JDBCUtils.closeResource(connection,preparedStatement,null);设置数据不默认提交,先传送,等着传完了统一提交。因为提交的时候也需要耗费时间。
作用:封装了针对于数据表的通用的操作。增删改查这些操作。



