继MySQL之后的技术-JDBC-从浅到深-02
目录
概念
编程六部曲
SQL注入和statement
工具类的封装
JDBC事务
模糊查询
批处理
数据库连接池
Apache-DBUtils
BasicDao
概念
JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序得到数据库系统,从而完成对数据库的各种操作。
解耦合:降低程序的耦合度,提高程序的扩展力。
编程六部曲
第一步,注册驱动
第二步,获取连接
第三步,获取数据库操作对象
第四步,执行sql语句
第五步,处理查询结果集
第六步,释放资源
public static void main(String[] args) throws Exception{//类加载Class.forName("com.mysql.jdbc.Driver");//获取连接String url = "jdbc:mysql://localhost:3306/mydata";Connection connection = DriverManager.getConnection(url, "root", "123456");//获取数据库对象Statement statement = connection.createStatement();//执行sql语句String sql = "select no,name from student";ResultSet resultSet = statement.executeQuery(sql);//处理结果集while(resultSet.next()){int no = resultSet.getInt(1);String name = resultSet.getString(2);System.out.println(name + ":\t" + no);}//关闭流resultSet.close();statement.close();connection.close();
}
SQL注入和statement
目前存在的问题:用户输入的信息中患有sql语句的关键字,并且这些关键字参与sql语句的编译过程导致sql语句的愿意被扭曲,进而达到sql注入。
如何解决sql注入问题?
1、主要用户提供的信息不参与sql语句的编译过程,问题就解决了。
2、即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,不起作用。
3、想要用户信息不参与sql语句的编译,那么必须使用java.sql.PreparedStatement
4、PreparedStatement接口继承了java.sql.Statement
5、PreparedStatement是属于预编译的数据库操作对象
6、PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值
解决SQL注入的关键是什么?
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用。
对比Statement和PreparedStatement
Statement存在sql注入问题,PreparedStatement解决了SQL注入问题。
Statement是编译一次执行一次。
PreparedStatement是编译一次,可执行N次,PreparedStatement效率较高。
PreparedStatement使用较多,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement。
工具类的封装
public class JDBCUtil{private static String url;private static String user;private static String password;private static String driver;static{try{Properties properties = new Properties();properties.load(new FileInputStream("mysql.properties"));driver = properties.getProperty("driver");url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");//注册驱动Class.forName(driver);}catch (Exception e){throw new RuntimeException(e);}}//创建连接方法public static Connection getConnection(){try{return DriverManager.getConnection(url,user,password);} catch(SQLException throwables) {throw new RuntimeException(throwables);}}//关闭流public static void close(ResultSet rs, Statement ps,Connection c){if(rs != null){try{rs.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(ps != null){try{ps.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(c != null){try{c.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}}public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;String sql = "insert into t1 values('李四',2)";try{preparedStatement = connection.prepareStatement(sql);//执行sql语句preparedStatement.executeUpdate();} catch(SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(null,preparedStatement,connection);}}}
JDBC事务
JDBC中的事务是自动提交的,什么时候自动提交?
只要执行任意一条DML预计,则自动提交一次,这是JDBC默认的事务行为。但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。
//重点三行代码
connection.setAutoCommit(false);
connection.commit();
connection.rollback();
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;try {//设置为不自动提交,即开启事务connection.setAutoCommit(false);String sql = "update t1 set no = no - 100 where name = '马云'" ;preparedStatement = connection.prepareStatement(sql);preparedStatement.executeUpdate();String sql2 = "update t1 set no = no + 100 where name = '马化腾'";PreparedStatement preparedStatement1 = connection.prepareStatement(sql2);preparedStatement1.executeUpdate();//提交事务connection.commit();} catch (SQLException throwables){try{//程序执行到此处,说明程序报错了connection.rollback();} catch (SQLException e){e.printStackTrace();}throwables.printStackTrace();} finally{JDBCUtil.close(null, preparedStatement, connection);}
}
模糊查询
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;ResultSet resultSet = null;try{String sql = "select name fron t1 where name like ?";preparedStatement = connection.prepareStatement(sql);//模糊查询preparedStatement.setString(1,"马%");resultSet = preparedStatement.executeQuery();while(resultSet.next()){String name = resultSet.getString("name");System.out.println(name);}} catch(SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(resultSet, preparedStatement, connection);}
}
批处理
1、当需要成批插入或者更新记录时,可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。
2、JDBC的批量处理语句包括以下方法:
addBatch(); //添加需要批量处理的SQL语句或参数
executeBatch(); //执行批量处理语句
clearBatch(); // 清空批处理包下的语句
3、JDBC连接MySQL时,如果要使用批处理功能,请在url后添加:
?rewriteBatchedStatements=true
4、批处理往往和PreparedStatement一起搭配使用,减少变异次数,减少运行次数。
//以下是传统方法批量传入大量数据
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;try{String sql = "insert into t1 values(?,?)";preparedStatement = connection.prepareStatement(sql);long start = System.currentTimeMillis();//批量插入数据for(int i = 0;i < 5000;i++){prepreadStatement.setString(1,"冻梨");preparedStatement.setInt(2,i);preparedStatement.executeUpdate();}long end = System.currentTimeMillis();} catch(SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(null, preparedStatement, connection);}
}
//以下是批处理方法
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;try {String sql = "insert into t1 values(?,?)";preparedStatement = connection.preparedStatement(sql);long start = System.currentTimeMillis();//批量插入数据for(int i = 15000; i< 20000;i++){preparedStatement.setString(1,"冻梨");preparedStatement.setInt(2,i);//将sql语句加入批处理包内preparedStatement.addBatch();//达到一千条,在进行处理if((i + 1) % 1000 == 0){preparedStatement.executeBatch();preparedStatement.clearBatch();}}long end = System.currentTimeMillis();} catch (SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(null,preparedStatement, connection);}
}
数据库连接池
1、传统的JDBC数据库连接使用DriverManager来获取,每次向数据库连接的时候都要将Connection加载到内容中,再验证IP地址,用户名和密码需要数据库连接,会占用很多系统资源。
2、每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
3、传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
4、解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
数据库连接池基本介绍:
1、预先在缓冲池放入一定数据的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完毕之后再放回去。
2、数据库连接池负责分配,管理和释放数据库连接,他允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
3、当应用程序向连接池请求的连接数超过最大连接数据时,需要等待。
JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口。
数据库连接池种类:
1、C3P0:速度慢,稳定
2、DBCP:较快,不稳定
3、Proxool:有监控连接池窗台的功能,不稳定
4、BonCP:数据快
5、Druid:集以上优点于一身
//传统方式
public static void main(String[] args) throws Exception{long start = System.currentTimeMillis();for(int i = 0;i < 5000;i++){Connection connection = JDBCUtil.getConnection();connection.close();}long end = System.currentTimeMillis();}
//连接池,需要加入对应的jar和配置文件
public static void main(String[] args) throws Exception{//获取数据源对象ComboPooledDataSource comboPoolDataSource = new ComboPooledDataSource();//根据配置文件获取信息Properties properties = new Properties();properties.load(new FileInputStream("src//mysql.properties"));String driver = properties.getProperty('driver');String url = properties.getProperty('url');String user = properties.getProperty('user');String password = properties.getProperty('password');//给数据源设置参数comboPooledDataSource.setDriverClass(driver);comboPooledDataSource.setJdbcUrl(url);comboPooledDataSource.setUser(user);comboPooledDataSource.setPassword(password);//初始化连接数comboPooledDataSource.setInitialPoolSize(10);//设置最大连接数comboPooledDataSource.setMaxPoolSize(50);//获取连接for(int i = 0;i < 5000;i++){Connection connection = comboPooledDataSource.getConnection();connection.close();}}
//比如50w,Druid要快
public static void main(String[] args) throws Exception{Properties properties = new Properties();properties.load(new FileInputStream("src//druid.properties"));//创建一个Druid连接DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);for(int i = 0;i < 5000;i++){Connection connection = dataSource.getConnection();connection.close();}}
//关于Druid连接池的工具类
public class JDBCUtilsByDruid{private static DataSource ds;static {try{Properties properties = new Properties();properties.load(new FileInputStream("src//druid.properties"));ds = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e){throw new RuntimeException(e);}}//创建连接public static Connection getConnection(){try{return ds.getConnection();} catch(SQLException throwables){throw new RuntimeException(throwables);}}//不是真正的关闭,而是放回连接池public static void close(ResultSet rs, Statement ps, Connection c){if(rs != null){try{rs.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(ps != null){try{ps.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(c!= null){try{c.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}}
}
Apache-DBUtils
//以下是土方法把resultSet集合里的数据,封装到list集合中
public class T2{private Integer no;private String name;public T2(){}public T2(Integer no, String name){this.no = no;this.name = name;}public Integer getNo(){return no;}public static void main(String[] args){Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;String sql = "select * from t2";//存储结果ArrayList<T2> list = new ArrayList<>();try{connection = JDBCUtilsByDruid.getConnection();preparedStatement = connection.prepareStatement(sql);resultSet = preparedStatement.executeQuery();while(resultSet.next()){int no = resultSet.getInt(1);String name = resultSet.getString(2);list.add(new T2(no,name));}} catch(SQLExceotion throwables){throwables.printStackTrace();} finally{JDBCUtilByDruid.close(resultSet, preparedStatement, connection);}}
}
Apache-DBUtils:是Apache组织提供的一个开源JDBC工具类库,是对JDBC的封装,极大简化JDBC工作量。
Dbutils类:
1、QueryRunner类:该类封装了SQL的执行,是线程安全的。
2、ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。
ArrayHandler:把结果集中的第一行数据转成对象数组
ArrayListJamdler:把结果集中的每一行数据都换成一个数组,存放到List中
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中
BeanListHandler:将结果集中的第一行数据都封装到一个对应得到JavaBean实例中,存放到List
ColumnListHandler:将结果集中某一列的数据存放到List中
KeyedHandler:将结果集中的每行数据都封装到Map里,再把这些map再存放到一个map里,其key为指定的key
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
public static void main(String[] args) throws SQLException{Connection connection = JDBCUtilsByDruid.getConnection();QueryRunner queryRunner = new QueryRunner();String sql = "select * from t2";/*第一个参数:一个连接对象第二个参数:sql语句第三个参数:new BeanListHandler<>(T2.class)在将resultSet封装成T2对象,然后加入list集合中底层使用反射机制,去获取T2类的属性,进行封装*/List<T2> list = queryRunner.query(connection, sql, new BeanListHandler<>(T2.class));for(T2 t: list){System.out.println(t);}JDBCUtilsByDruid.close(null,null,connection);
}
public static void main(String[] args) throws SQLException{Connection connection = JDBCUtilsByDruid.getConnection();QueryRunner queryRunner = new QueryRunner();String sql = "delete from t2 where no = 3";int update = queryRunner.update(connection, sql);JDBCUtilsByDruid.close(null,null,connection);
}
BasicDao
DAO和增上改查通用方法——BasicDao
1、DAO:data access object 数据访问对象
2、这样的通用类,成为BasicDao,是专门和数据库交互的,即完成对数据表的crud操作
3、在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能
Customemer表——Customer.java——CustomerDao,java
Apache-dbutils+Druid简化了JDBC开发,但还有不足:
1、SQL语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行CRUD
2、对于select操作,如果有返回值,返回类型不能固定,需要使用泛型
3、将来的表很多,业务需求复杂,不可能只靠一个java类完成
public class BasicDAO<T> {private QueryRunner qr = new QueryRunner();//dml操作语句public int update(String sql, Object... parameters){Connection connection = null;try {connection = JDBCUtilsByDruid.getConnection();return qr.update(connection, sql, parameters);} catch (SQLException e){throw new RuntimeException(e);} finally {JDBCUtilsByDruid.close(null,null,connection);}}//根据传入的Class对象,返回对应泛型的集合public List<T> query(String ssql, Class<T> clazz, Object... parameters){Connection connection = null;try {connection = JDBCUtilsByDruid.getConnection();return qr.query(connection, sql, new BeanListHandler<T>(clazz),parameters);} catch (SQLException e){throw new RuntimeException(e);} finally{JDBCUtilsByDruid.close(null,null,connection);}}//查询单行结果public T querySingle(String sql, Class<T> clazz,Object... parameters){Connection connection = null;try{conenction = JDBCUtilsByDruid.getConnection();return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);} catch (SQLException e){throw new RuntimeException(e);} finally {JDBCUtilsByDruid.close(null,null,connection);}}//查询单行单列结果public Object queryScalar(String sql, Object... parameters){Connection connection = null;try{conenction = JDBCUtilsByDruid.getConnection();return qr.query(connection, sql, new ScalarHandler(), parameters);} catch (SQLException e){throw new RuntimeException(e);} finally {JDBCUtilsByDruid.close(null,null,connection);}}}
public class T2DAO extends BasicDAO<T2>{}public class T2{private Integer no;private String name;public T2(){}public T2(Integer no, String name){this.no = no;this.name = name;}public Integer getNo(){ return no;}public void setNo(Integer no) {this.no = no;}public String getName() {return name;}public void setName(String name){this.name = name;}@Overridepublic String toString(){}
}public class TestDao{public static void main(String[] args){T2DAO t2DAO = new T2DAO();//查询多行String sql = "select * from t2";List<T2> query = t2DAO.query(sql, T2.class);for(T2 t: query){System.out.println(t);}//查询单行 String sql3 = "select * from t2 where no = 2";T2 t2 = t2DAO.querySingle(sql3, T2.class);//查询单行单列String sql4 = "select name fron t2 where no = 2";Object o = t2DAO.queryScalar(sql4);//dml操作String sql2 = "insert into t2 values(4, '擦原配')";int update = t2DAO.update(sql2);}
}
public class JDBCUtilsByDruid{private static DataSource ds;static{try{Properties properties = new Properties();properties.load(new FileInputStream("src//druid.properties"));ds = DruidDataSourceFactory.createDataSource(Properties);} catch (Exception e){throw new RuntimeException(e);}}public static Connection getConnection(){try{return ds.getConnection();} catch (SQLException throwables){throw new RuntimeException(throwables);}}
}