第一章 JDBC概述

之前我们学习了JavaSE,编写了Java程序,数据保存在变量、数组、集合等中,无法持久化,后来学习了IO流可以将数据写入文件,但不方便管理数据以及维护数据的关系;

后来我们学习了数据库管理软件MySQL,可以方便的管理数据。

那么如何将它俩结合起来呢?即Java程序<==>MySQL,实现数据的存储和处理。

那么就可以使用JDBC技术。

1.1 JDBC概述

JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。

JDBC笔记 - 图1

即JDBC技术包含两个部分:

(1)java.sql包和javax.sql包中的API

因为为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。

(2)各个数据库厂商提供的jar

因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

1.2 JDBC使用步骤

代码编写步骤:

JDBC访问数据库步骤

1:注册一个Driver驱动

三部曲:

  1. 1)将DBMS数据库管理软件的驱动jar拷贝到项目的libs目录中
  2. 例如:mysql-connector-java-5.1.36-bin.jar
  3. 2)把驱动jar添加到项目的build path
  4. 3)将驱动类加载到内存中
  5. Class.forName("com.mysql.jdbc.Driver");

2:创建数据库连接(Connection)

  1. Connection conn = DriverManager.getConnection(url,username,password);
  2. mysqlurljdbc:mysql://localhost:3306/数据库名?参数名=参数值
  3. jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8
  4. (如果JDBC程序与服务器端 的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集

3:创建SQL命令发送器Statement

  1. 创建StatementPreparedStatement对象

4:通过Statement发送SQL命令并得到结果

5:处理结果(select语句)

6:关闭数据库资源ResultSet Statement Connection

JDBC笔记 - 图2

相关的API:

1、DriverManager:驱动管理类

2、Connection:代表数据库连接

3、Statement和PreparedStatement:用来执行sql

  1. 执行增、删、改:int executeUpate()
  2. 执行查询:ResultSet executeQuery()

4、如何遍历ResultSet ?

  1. 1boolean next():判断是否还有下一行
  2. 2getString(字段名或序号),getInt(字段名或序号),getObject(字段名或序号)

示例代码1:增、删、改

  1. public class TestJDBC {
  2. public static void main(String[] args) throws ClassNotFoundException, SQLException {
  3. //1、注册驱动
  4. //(1)方式一:Class.forName("驱动类的全名称")
  5. Class.forName("com.mysql.jdbc.Driver");
  6. // (2)创建驱动类的对象
  7. // new com.mysql.jdbc.Driver();//硬编码
  8. //(3)通过DriverManager注册驱动
  9. // DriverManager.registerDriver(new com.mysql.jdbc.Driver());//硬编码
  10. //2、获取连接,连接数据库
  11. //TCP/IP协议编程,需要服务器的IP地址和端口号
  12. //mysql的url格式:jdbc协议:子协议://主机名:端口号/要连接的数据库名
  13. String url = "jdbc:mysql://localhost:3306/test";//其中test是数据库名
  14. String user = "root";
  15. String password = "123456";
  16. Connection conn = DriverManager.getConnection(url, user, password);
  17. //3、执行sql
  18. //添加一个部门到数据库的t_department表中
  19. //(1)编写sql
  20. String sql = "insert into t_department values(null,'计算部2','计算钞票2')";
  21. /*
  22. * 回忆: TCP/IP程序时
  23. * Socket代表连接
  24. * socket.getOutputStream()来发送数据,
  25. * socket.getInputStream()来接收数据
  26. *
  27. * 可以把Connection比喻成Socket
  28. * 把Statement比喻成OutputStream
  29. */
  30. //(2)获取Statement对象
  31. Statement st = conn.createStatement();
  32. //(3)执行sql
  33. int len = st.executeUpdate(sql);
  34. //(4)处理结果
  35. System.out.println(len>0?"成功":"失败");
  36. //4、关闭
  37. st.close();
  38. conn.close();
  39. }
  40. }

示例代码2:查询

  1. public class TestSelect {
  2. public static void main(String[] args) throws Exception{
  3. // 1、注册驱动
  4. Class.forName("com.mysql.jdbc.Driver");
  5. // 2、连接数据库
  6. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  7. // 3、执行sql
  8. String sql = "SELECT * FROM t_department";
  9. Statement st = conn.createStatement();
  10. ResultSet rs = st.executeQuery(sql);//ResultSet看成InputStream
  11. while(rs.next()){//next()表示是否还有下一行
  12. Object did = rs.getObject(1);//获取第n列的值
  13. Object dname = rs.getObject(2);
  14. Object desc = rs.getObject(3);
  15. /*
  16. int did = rs.getInt("did");//也可以根据列名称,并且可以按照数据类型获取
  17. String dname = rs.getString("dname");
  18. String desc = rs.getString("description");
  19. */
  20. System.out.println(did +"\t" + dname + "\t"+ desc);
  21. }
  22. // 4、关闭
  23. rs.close();
  24. st.close();
  25. conn.close();
  26. }
  27. }

第二章 使用PreparedStatement处理CRUD

2.1 通过PreparedStatement来解决Statement的问题

Statement的问题:通过PreparedStatement来代替

(1)sql拼接

  1. String sql = "insert into t_employee(ename,tel,gender,salary) values('" + ename + "','" + tel + "','" + gender + "'," + salary +")";
  2. Statement st = conn.createStatement();
  3. int len = st.executeUpdate(sql);

(2)sql注入

  1. String sql = "SELECT * FROM t_employee where ename='" + ename + "'";
  2. //如果我此时从键盘输入ename值的时候,输入:张三' or '1'= '1
  3. //结果会把所有数据都查询出来
  4. Statement st = conn.createStatement();
  5. ResultSet rs = st.executeQuery(sql);

(3)处理blob等类型的数据

  1. 创建含有Blob字段类型的表 测试插入
  2. String sql = "insert into user(username,photo) values('zs', 图片字节流)";
  3. //此时photo是blob类型的数据时,无法在sql中直接拼接

PreparedStatement解决问题:

(1)避免sql拼接

  1. String sql = "insert into t_employee(ename,tel,gender,salary) values(?,?,?,?)";
  2. PreparedStatement pst = conn.prepareStatement(sql);//这里要传带?的sql,然后mysql端就会对这个sql进行预编译
  3. //设置?的具体值
  4. /*pst.setString(1, ename);
  5. pst.setString(2, tel);
  6. pst.setString(3, gender);
  7. pst.setDouble(4, salary);*/
  8. pst.setObject(1, ename);
  9. pst.setObject(2, tel);
  10. pst.setObject(3, gender);
  11. pst.setObject(4, salary);
  12. int len = pst.executeUpdate();//此处不能传sql
  13. System.out.println(len);

(2)不会有sql注入

  1. String sql = "SELECT * FROM t_employee where ename=?";
  2. //即使输入'张三' or '1'= '1'也没问题
  3. PreparedStatement pst = conn.prepareStatement(sql);
  4. //中间加入设置?的值
  5. pst.setObject(1, ename);
  6. ResultSet rs = pst.executeQuery();

(3)处理blob类型的数据

  1. String sql = "insert into user(username,photo) values(?,?)";
  2. PreparedStatement pst = conn.prepareStatement(sql);
  3. //设置?的值
  4. pst.setObject(1, "zs");
  5. FileInputStream fis = new FileInputStream("D:/QMDownload/img/美女/15.jpg");
  6. pst.setBlob(2, fis);
  7. int len = pst.executeUpdate();
  8. System.out.println(len>0?"成功":"失败");
  • 注意两个问题:
    ①my.ini关于上传的字节流文件有大小限制,可以在my.ini中配置变量
    max_allowed_packet=16M
    ②每一种blob有各自大小限制:
    tinyblob:255字节、blob:65k、mediumblob:16M、longblob:4G

2.2 获取自增长键值

  1. /*
  2. * 我们通过JDBC往数据库的表格中添加一条记录,其中有一个字段是自增的,那么在JDBC这边怎么在添加之后直接获取到这个自增的值
  3. * PreparedStatement是Statement的子接口。
  4. * Statement接口中有一些常量值:
  5. * (1)Statement.RETURN_GENERATED_KEYS
  6. *
  7. * 要先添加后获取到自增的key值:
  8. * (1)PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
  9. * (2)添加sql执行完成后,通过PreparedStatement的对象调用getGeneratedKeys()方法来获取自增长键值,遍历结果集
  10. * ResultSet rs = pst.getGeneratedKeys();
  11. */
  12. public class TestAutoIncrement {
  13. public static void main(String[] args) throws Exception{
  14. //1、注册驱动
  15. Class.forName("com.mysql.jdbc.Driver");
  16. //2、获取连接
  17. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  18. //3、执行sql
  19. String sql = "insert into t_department values(null,?,?)";
  20. /*
  21. * 这里在创建PreparedStatement对象时,传入第二个参数的作用,就是告知服务器端
  22. * 当执行完sql后,把自增的key值返回来。
  23. */
  24. PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
  25. //设置?的值
  26. pst.setObject(1, "测试部");
  27. pst.setObject(2, "测试项目数据");
  28. //执行sql
  29. int len = pst.executeUpdate();//返回影响的记录数
  30. if(len>0){
  31. //从pst中获取到服务器端返回的键值
  32. ResultSet rs = pst.getGeneratedKeys();
  33. //因为这里的key值可能多个,因为insert语句可以同时添加多行,所以用ResultSet封装
  34. //这里因为只添加一条,所以用if判断
  35. if(rs.next()){
  36. Object key = rs.getObject(1);
  37. System.out.println("自增的key值did =" + key);
  38. }
  39. }
  40. //4、关闭
  41. pst.close();
  42. conn.close();
  43. }
  44. }

2.3 批处理

需要在url的末尾添加此参数:rewriteBatchedStatements=true

  1. /*
  2. * 批处理:
  3. * 批量处理sql
  4. *
  5. * 例如:
  6. * (1)订单明细表的多条记录的添加
  7. * (2)批量添加模拟数据
  8. * ...
  9. *
  10. * 不用批处理,和用批处理有什么不同?
  11. * 批处理的效率很多
  12. *
  13. * 如何进行批处理操作?
  14. * (1)在url中要加一个参数
  15. * rewriteBatchedStatements=true
  16. * 那么我们的url就变成了 jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
  17. * 这里的?,表示?后面是客户端给服务器端传的参数,多个参数直接使用&分割
  18. * (2)调用方法不同
  19. * pst.addBatch();
  20. * int[] all = pst.executeBatch();
  21. *
  22. * 注意:如果批量添加时,insert使用values,不要使用value
  23. */
  24. public class TestBatch {
  25. public static void main(String[] args) throws Exception{
  26. long start = System.currentTimeMillis();
  27. //例如:在部门表t_department中添加1000条模拟数据
  28. //1、注册驱动
  29. Class.forName("com.mysql.jdbc.Driver");
  30. //2、获取连接
  31. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true", "root", "123456");
  32. //3、执行sql
  33. String sql = "insert into t_department values(null,?,?)";
  34. PreparedStatement pst = conn.prepareStatement(sql);
  35. //设置?的值
  36. for (int i = 1; i <=1000; i++) {
  37. pst.setObject(1, "模拟部门"+i);
  38. pst.setObject(2, "模拟部门的简介"+i);
  39. pst.addBatch();//添加到批处理一组操作中,攒一块处理
  40. /* if(i % 500 == 0){//有时候也攒一部分,执行一部分
  41. //2.执行
  42. pst.executeBatch();
  43. //3.清空
  44. pst.clearBatch();
  45. }*/
  46. }
  47. pst.executeBatch();
  48. //4、关闭
  49. pst.close();
  50. conn.close();
  51. long end = System.currentTimeMillis();
  52. System.out.println("耗时:" + (end - start));//耗时:821
  53. }
  54. }

2.4 事务

  1. /*
  2. * mysql默认每一个连接是自动提交事务的。
  3. * 那么当我们在JDBC这段,如果有多条语句想要组成一个事务一起执行的话,那么在JDBC这边怎么设置手动提交事务呢?
  4. * (1)在执行之前,设置手动提交事务
  5. * Connection的对象.setAutoCommit(false)
  6. * (2)成功:
  7. * Connection的对象.commit();
  8. * 失败:
  9. * Connection的对象.rollback();
  10. *
  11. * 补充说明:
  12. * 为了大家养成要的习惯,在关闭Connection的对象之前,把连接对象设置回自动提交
  13. * (3)Connection的对象.setAutoCommit(true)
  14. *
  15. * 因为我们现在的连接是建立新的连接,那么如果没有还原为自动提交,没有影响。
  16. * 但是我们后面实际开发中,每次获取的连接,不一定是新的连接,而是从连接池中获取的旧的连接,而且你关闭也不是真关闭,而是还给连接池,供别人接着用。以防别人拿到后,以为是自动提交的,而没有commit,最终数据没有成功。
  17. */
  18. public class TestTransaction {
  19. public static void main(String[] args) throws Exception{
  20. /*
  21. * 一般涉及到事务处理的话,那么业务逻辑都会比较复杂。
  22. * 例如:购物车结算时:
  23. * (1)在订单表中添加一条记录
  24. * (2)在订单明细表中添加多条订单明细的记录(表示该订单买了什么东西)
  25. * (3)修改商品表的销量和库存量
  26. * ...
  27. * 那么我们今天为了大家关注事务的操作,而不会因为复杂的业务逻辑的影响导致我们的理解,那么我们这里故意
  28. * 用两条修改语句来模拟组成一个简单的事务。
  29. * update t_department set description = 'xx' where did = 2;
  30. * update t_department set description = 'yy' where did = 3;
  31. *
  32. * 我希望这两天语句要么一起成功,要么一起回滚
  33. * 为了制造失败,我故意把第二条语句写错
  34. * update t_department set description = 'yy' (少了where) did = 3;
  35. */
  36. //1、注册驱动
  37. Class.forName("com.mysql.jdbc.Driver");
  38. //2、获取连接
  39. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  40. //设置手动提交事务
  41. conn.setAutoCommit(false);
  42. //3、执行sql
  43. String sql1 = "update t_department set description = 'xx' where did = 2";
  44. String sql2 = "update t_department set description = 'yy' did = 3";//这是错的
  45. //使用prepareStatement的sql也可以不带?
  46. PreparedStatement pst = null;
  47. try {
  48. pst = conn.prepareStatement(sql1);
  49. int len = pst.executeUpdate();
  50. System.out.println("第一条:" + (len>0?"成功":"失败"));
  51. pst = conn.prepareStatement(sql2);
  52. len = pst.executeUpdate();
  53. System.out.println("第二条:" + (len>0?"成功":"失败"));
  54. //都成功了,就提交事务
  55. System.out.println("提交");
  56. conn.commit();
  57. } catch (Exception e) {
  58. System.out.println("回滚");
  59. //失败要回滚
  60. conn.rollback();
  61. }
  62. //4、关闭
  63. pst.close();
  64. conn.setAutoCommit(true);//还原为自动提交
  65. conn.close();
  66. }
  67. }

第三章 数据库连接池

1、什么是数据库连池

连接对象的缓冲区。负责申请,分配管理,释放连接的操作。

2、为什么要使用数据库连接池

Connection对象在每次执行DML和DQL的过程中都要创建一次,DML和DQL执行完毕后,connection对象都会被销毁. connection对象是可以反复使用的,没有必要每次都创建新的.该对象的创建和销毁都是比较消耗系统资源的,如何实现connection对象的反复使用呢?使用连接池技术实现。

3.连接池的优势

  • 预先准备一些链接对象,放入连接池中,当多个线程并发执行时,可以避免短时间内一次性大量创建链接对象,减少计算机单位时间内的运算压力,提高程序的响应速度
  • 实现链接对象的反复使用,可以大大减少链接对象的创建次数,减少资源的消耗

JDBC笔记 - 图3

JDBC笔记 - 图4

4、市面上有很多现成的数据库连接池技术:

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:

    • DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池

5、阿里的德鲁伊连接池技术

(1)加入jar包

例如:druid-1.1.10.jar

(2)代码步骤

第一步:建立一个数据库连接池

第二步:设置连接池的参数

第三步:获取连接

  1. public class TestPool {
  2. public static void main(String[] args) throws SQLException {
  3. //1、创建数据源(数据库连接池)对象
  4. DruidDataSource ds =new DruidDataSource();
  5. //2、设置参数
  6. //(1)设置基本参数
  7. ds.setDriverClassName("com.mysql.jdbc.Driver");
  8. ds.setUrl("jdbc:mysql://localhost:3306/test");
  9. ds.setUsername("root");
  10. ds.setPassword("123456");
  11. //(2)设置连接数等参数
  12. ds.setInitialSize(5);//一开始提前申请好5个连接,不够了,重写申请
  13. ds.setMaxActive(10);//最多不超过10个,如果10都用完了,还没还回来,就会出现等待
  14. ds.setMaxWait(1000);//用户最多等1000毫秒,如果1000毫秒还没有人还回来,就异常了
  15. //3、获取连接
  16. for (int i = 1; i <=15; i++) {
  17. Connection conn = ds.getConnection();
  18. System.out.println("第"+i+"个:" + conn);
  19. //如果这里没有关闭,就相当于没有还
  20. // conn.close();#这里关闭,是还回池中
  21. }
  22. }
  23. }
配置 缺省 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

第四章 封装JDBCTools

配置文件:src/jdbc.properties

  1. #key=value
  2. driverClassName=com.mysql.jdbc.Driver
  3. url=jdbc:mysql://localhost:3306/test
  4. username=root
  5. password=123456
  6. initialSize=5
  7. maxActive=10
  8. maxWait=1000

JDBCTools工具类:

  1. package com.atguigu.util;
  2. import java.sql.Connection;
  3. import java.sql.SQLException;
  4. import java.util.Properties;
  5. import javax.sql.DataSource;
  6. import com.alibaba.druid.pool.DruidDataSourceFactory;
  7. /*
  8. * 获取连接或释放连接的工具类
  9. */
  10. public class JDBCTools {
  11. // 1、数据源,即连接池
  12. private static DataSource dataSource;
  13. // 2、ThreadLocal对象
  14. private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
  15. static {
  16. try {
  17. //1、读取druip.properties文件
  18. Properties pro = new Properties();
  19. pro.load(JDBCTools2.class.getClassLoader().getResourceAsStream("druid.properties"));
  20. //2、创建线程池
  21. dataSource = DruidDataSourceFactory.createDataSource(pro);
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. /**
  27. * 获取连接的方法
  28. *
  29. * @return
  30. * @throws SQLException
  31. */
  32. public static Connection getConnection() {
  33. // 从当前线程中获取连接
  34. Connection connection = threadLocal.get();
  35. if (connection == null) {
  36. // 从连接池中获取一个连接
  37. try {
  38. connection = dataSource.getConnection();
  39. // 将连接与当前线程绑定
  40. threadLocal.set(connection);
  41. } catch (SQLException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. return connection;
  46. }
  47. /**
  48. * 释放连接的方法
  49. *
  50. * @param connection
  51. */
  52. public static void releaseConnection() {
  53. // 获取当前线程中的连接
  54. Connection connection = threadLocal.get();
  55. if (connection != null) {
  56. try {
  57. connection.close();
  58. } catch (SQLException e) {
  59. e.printStackTrace();
  60. }finally{
  61. // 将已经关闭的连接从当前线程中移除
  62. threadLocal.remove();
  63. }
  64. }
  65. }
  66. }

ThreadLocal:

JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session等

ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap,其key就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

第五章 封装BasicDAOImpl

  1. package com.atguigu.test08.dao;
  2. import java.lang.reflect.Field;
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.ResultSetMetaData;
  7. import java.sql.SQLException;
  8. import java.util.ArrayList;
  9. import com.atguigu.util.JDBCTools;
  10. public class BasicDAOImpl {
  11. /*
  12. * 通用的增、删、改的方法
  13. */
  14. protected int update(String sql,Object... args) throws SQLException{
  15. //1、获取链接对象
  16. Connection conn = JDBCTools.getConnection();
  17. //2、编写sql,用形参传入的方式代替
  18. //3、获取Statement或PreparedStatement对象
  19. PreparedStatement pst = conn.prepareStatement(sql);
  20. //设置?的值
  21. //因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
  22. //Object... args可变形参,可以传入0~n个值
  23. //如果没有传入,说明没有?需要设置
  24. //如果传入了n个值,那么说明sql中有n个?需要设置
  25. if(args!=null && args.length>0){
  26. for (int i = 0; i < args.length; i++) {
  27. //数组的下标从0开始,pst的?的序号是从1开始,所以这里用i+1
  28. pst.setObject(i+1, args[i]);
  29. }
  30. }
  31. //4、执行sql
  32. int len = pst.executeUpdate();
  33. //5、关闭
  34. pst.close();
  35. //这里不关闭conn,因为它在同一个事务的其他地方还要使用
  36. return len;
  37. }
  38. /*
  39. * 通用查询多个Javabean对象的方法
  40. */
  41. protected <T> ArrayList<T> getList(Class<T> clazz,String sql,Object... args) throws Exception{
  42. //1、获取链接对象
  43. Connection conn = JDBCTools.getConnection();
  44. //2、编写sql,由形参传入
  45. //3、获取PreparedStatement对象
  46. PreparedStatement pst = conn.prepareStatement(sql);
  47. //4、设置?,由形参传入
  48. if(args!=null && args.length>0){
  49. for (int i = 0; i < args.length; i++) {
  50. //数组的下标从0开始,pst的?的序号是从1开始,所以这里用i+1
  51. pst.setObject(i+1, args[i]);
  52. }
  53. }
  54. //5、执行sql
  55. ResultSet rs = pst.executeQuery();
  56. /*
  57. * 如何把ResultSet结果集中的数据变成一个一个的Javabean对象,放到ArrayList对象,并且返回
  58. */
  59. ArrayList<T> list = new ArrayList<>();
  60. /*
  61. * 要从ResultSet结果集中获取一共有几行,决定要创建几个对象
  62. * 要从ResultSet结果集中获取一共有几列,决定要为几个属性赋值
  63. * ResultSet结果集对象中,有一个方法ResultSetMetaData getMetaData()获取结果集的元数据
  64. * 元数据就是描述结果集中的数据的数据,例如:列数,列名称等
  65. */
  66. ResultSetMetaData metaData = rs.getMetaData();
  67. int count = metaData.getColumnCount();//获取列数
  68. while(rs.next()){//循环一次代表一行,就要创建一个Javabean对象
  69. //(1)创建一个Javabean对象
  70. T t = clazz.newInstance();//这个方法有要求,要求Javabean这个类要有无参构造
  71. //(2)设置对象的属性值
  72. /*
  73. * 反射操作属性的步骤:
  74. * ①获取Class对象,现在有了
  75. * ②获取属性对象Field
  76. * Field f = clazz.getDeclaredField("属性名");
  77. * ③创建Javabean对象,已经创建
  78. * ④设置属性的可访问性 setAccessible(true)
  79. * ⑤设置属性的值
  80. */
  81. for (int i = 0; i < count; i++) {//一共要为count个属性赋值
  82. // Field f = clazz.getDeclaredField("属性名");
  83. String fieldName = metaData.getColumnLabel(i+1);//获取第几列的字段名
  84. Field f = clazz.getDeclaredField(fieldName);
  85. f.setAccessible(true);
  86. f.set(t, rs.getObject(i+1));//rs.getObject(i+1)获取第几列的值
  87. }
  88. //(3)把Javabean对象放到list中
  89. list.add(t);
  90. }
  91. pst.close();
  92. rs.close();
  93. //这里不关闭conn,因为它在同一个事务的其他地方还要使用
  94. return list;
  95. }
  96. protected <T> T getBean(Class<T> clazz,String sql,Object... args) throws Exception{
  97. return getList(clazz,sql,args).get(0);
  98. }
  99. }

使用BasicDAOImpl实现Employee的增删改查

JDBC笔记 - 图5

示例代码:EmployeeDAO.java

  1. public interface EmployeeDAO {
  2. void addEmployee(Employee emp);
  3. void updateEmployee(Employee emp);
  4. void deleteByEid(int eid);
  5. Employee getByEid(int eid);
  6. ArrayList<Employee> getAll();
  7. }

示例代码:EmployeeDAOImpl.java

  1. package com.atguigu.test08.dao;
  2. import java.sql.SQLException;
  3. import java.util.ArrayList;
  4. import com.atguigu.bean.Employee;
  5. public class EmployeeDAOImpl extends BasicDAOImpl implements EmployeeDAO {
  6. @Override
  7. public void addEmployee(Employee emp) {
  8. //`ename`,`tel`,`gender`,`salary`,`commission_pct`,`birthday`,
  9. //`hiredate`,`job_id`,`email`,`mid`,`address`,`native_place`,`did`
  10. String sql = "insert into t_employee values(null,?,?,?,?,?,?,?,?,?,?,?,?,?)";
  11. try {
  12. update(sql, emp.getEname(),emp.getTel(),emp.getGender(),emp.getSalary(),emp.getCommissionPct(),
  13. emp.getBirthday(),emp.getHiredate(),emp.getJobId(),emp.getEmail(),emp.getMid(),emp.getAddress(),
  14. emp.getNativePlace(),emp.getDid());
  15. } catch (SQLException e) {
  16. throw new RuntimeException(e);
  17. }
  18. }
  19. @Override
  20. public void updateEmployee(Employee emp) {
  21. String sql = "update t_employee set `ename`=?,`tel`=?,`gender`=?,`salary`=?,`commission_pct`=?,`birthday`=?,`hiredate`=?,`job_id`=?,`email`=?,`mid`=?,`address`=?,`native_place`=?,`did`=? where eid=?";
  22. try {
  23. update(sql, emp.getEname(),emp.getTel(),emp.getGender(),emp.getSalary(),emp.getCommissionPct(),
  24. emp.getBirthday(),emp.getHiredate(),emp.getJobId(),emp.getEmail(),emp.getMid(),emp.getAddress(),
  25. emp.getNativePlace(),emp.getDid(),emp.getEid());
  26. } catch (SQLException e) {
  27. throw new RuntimeException(e);
  28. }
  29. }
  30. @Override
  31. public void deleteByEid(int eid) {
  32. String sql = "delete from t_employee where eid=?";
  33. try {
  34. update(sql, eid);
  35. } catch (SQLException e) {
  36. throw new RuntimeException(e);
  37. }
  38. }
  39. @Override
  40. public Employee getByEid(int eid) {
  41. //这里sql中通过取别名的方式,来指定对应的Javabean的属性名
  42. String sql = "select `eid`,`ename`,`tel`,`gender`,`salary`,`commission_pct` commissionPct ,`birthday`,`hiredate`,`job_id` jobId,`email`,`mid`,`address`,`native_place` nativePlace,`did` from t_employee where eid=?";
  43. Employee emp = null;
  44. try {
  45. emp = getBean(Employee.class, sql, eid);
  46. } catch (Exception e) {
  47. throw new RuntimeException(e);
  48. }
  49. return emp;
  50. }
  51. @Override
  52. public ArrayList<Employee> getAll() {
  53. //这里sql中通过取别名的方式,来指定对应的Javabean的属性名
  54. String sql = "select `eid`,`ename`,`tel`,`gender`,`salary`,`commission_pct` commissionPct ,`birthday`,`hiredate`,`job_id` jobId,`email`,`mid`,`address`,`native_place` nativePlace,`did` from t_employee";
  55. ArrayList<Employee> list = new ArrayList<Employee>();
  56. try {
  57. list = getList(Employee.class, sql);
  58. } catch (Exception e) {
  59. throw new RuntimeException(e);
  60. }
  61. return list;
  62. }
  63. }

第六章 Apache的DBUtils

commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

其中QueryRunner类封装了SQL的执行,是线程安全的。

(1)可以实现增、删、改、查、批处理、

(2)考虑了事务处理需要共用Connection。

(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

(1)更新

public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。

……

(2)插入

public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值

….

(3)批处理

public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句

public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句

…..

(4)使用QueryRunner类实现查询

public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

….

ResultSetHandler接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet rs)该方法的返回值将作为QueryRunner类的query()方法的返回值。

该接口有如下实现类可以使用:

  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
  • ScalarHandler:查询单个值对象
  • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
  • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
  • ColumnListHandler:将结果集中某一列的数据存放到List中。
  • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
  • ArrayHandler:把结果集中的第一行数据转成对象数组。
  • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。

示例代码:BasicDAOImpl.java

  1. package com.atguigu.test09.dbutil;
  2. import java.sql.Connection;
  3. import java.sql.SQLException;
  4. import java.util.List;
  5. import java.util.Map;
  6. import org.apache.commons.dbutils.QueryRunner;
  7. import org.apache.commons.dbutils.handlers.BeanHandler;
  8. import org.apache.commons.dbutils.handlers.BeanListHandler;
  9. import org.apache.commons.dbutils.handlers.MapListHandler;
  10. import org.apache.commons.dbutils.handlers.ScalarHandler;
  11. import com.atguigu.util.JDBCTools;
  12. //在这里都不关闭连接,统一关闭,因为Connection对象需要共享使用
  13. public class BasicDAOImpl4 {
  14. private QueryRunner qr = new QueryRunner();
  15. protected int update(String sql,Object... args) throws SQLException{
  16. Connection conn = JDBCTools2.getConnection();
  17. int len = qr.update(conn, sql, args);
  18. return len;
  19. }
  20. protected <T> List<T> getList(Class<T> clazz,String sql,Object... args) throws Exception{
  21. Connection conn = JDBCTools2.getConnection();
  22. /*
  23. * ResultSetHandler接口,用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
  24. * (1)BeanListHandler等形式
  25. */
  26. List<T> list = qr.query(conn, sql, new BeanListHandler<>(clazz), args);
  27. return list;
  28. }
  29. protected <T> T getBean(Class<T> clazz,String sql,Object... args) throws Exception{
  30. Connection conn = JDBCTools2.getConnection();
  31. /*
  32. * ResultSetHandler接口,用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
  33. * (2)BeanHandler等形式
  34. */
  35. T t = qr.query(conn, sql, new BeanHandler<>(clazz), args);
  36. return t;
  37. }
  38. /*
  39. * 通用的查询单个值的方法
  40. * 例如:员工总数,最高工资,平均工资等
  41. */
  42. protected Object getObject(String sql,Object... args) throws Exception{
  43. Connection conn = JDBCTools2.getConnection();
  44. /*
  45. * ResultSetHandler接口,用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
  46. * (3)ScalarHandler:查询单个值对象等形式
  47. */
  48. Object obj = qr.query(conn, sql, new ScalarHandler<>(), args);
  49. return obj;
  50. }
  51. /*
  52. * 通用的查询多行多列的方法
  53. * 例如:每个部门的平均工资
  54. */
  55. protected List<Map<String, Object>> getMapList(String sql,Object... args) throws Exception{
  56. Connection conn = JDBCTools2.getConnection();
  57. /*
  58. * ResultSetHandler接口,用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
  59. * (4)MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
  60. */
  61. List<Map<String, Object>> list = qr.query(conn, sql, new MapListHandler(), args);
  62. return list;
  63. }
  64. }

示例代码:

  1. public interface EmployeeDAO2 {
  2. void addEmployee(Employee emp);
  3. void updateEmployee(Employee emp);
  4. void deleteByEid(int eid);
  5. Employee getByEid(int eid);
  6. List<Employee> getAll();
  7. long empCount();//查询员工总数
  8. double avgSalary();//查询全公司的平均工资
  9. //key是部门编号,Double是平均工资
  10. Map<Integer,Double> avgSalaryPerDepartment();
  11. }

示例代码:

  1. public class EmployeeDAOImpl2 extends BasicDAO2 implements EmployeeDAO2 {
  2. @Override
  3. public void addEmployee(Employee emp) {
  4. //`ename`,`tel`,`gender`,`salary`,`commission_pct`,`birthday`,
  5. //`hiredate`,`job_id`,`email`,`mid`,`address`,`native_place`,`did`
  6. String sql = "insert into t_employee values(null,?,?,?,?,?,?,?,?,?,?,?,?,?)";
  7. try {
  8. update(sql, emp.getEname(),emp.getTel(),emp.getGender(),emp.getSalary(),emp.getCommissionPct(),
  9. emp.getBirthday(),emp.getHiredate(),emp.getJobId(),emp.getEmail(),emp.getMid(),emp.getAddress(),
  10. emp.getNativePlace(),emp.getDid());
  11. } catch (SQLException e) {
  12. throw new RuntimeException(e);
  13. }
  14. }
  15. @Override
  16. public void updateEmployee(Employee emp) {
  17. String sql = "update t_employee set `ename`=?,`tel`=?,`gender`=?,`salary`=?,`commission_pct`=?,`birthday`=?,`hiredate`=?,`job_id`=?,`email`=?,`mid`=?,`address`=?,`native_place`=?,`did`=? where eid=?";
  18. try {
  19. update(sql, emp.getEname(),emp.getTel(),emp.getGender(),emp.getSalary(),emp.getCommissionPct(),
  20. emp.getBirthday(),emp.getHiredate(),emp.getJobId(),emp.getEmail(),emp.getMid(),emp.getAddress(),
  21. emp.getNativePlace(),emp.getDid(),emp.getEid());
  22. } catch (SQLException e) {
  23. throw new RuntimeException(e);
  24. }
  25. }
  26. @Override
  27. public void deleteByEid(int eid) {
  28. String sql = "delete from t_employee where eid=?";
  29. try {
  30. update(sql, eid);
  31. } catch (SQLException e) {
  32. throw new RuntimeException(e);
  33. }
  34. }
  35. @Override
  36. public Employee getByEid(int eid) {
  37. //这里sql中通过取别名的方式,来指定对应的Javabean的属性名
  38. String sql = "select `eid`,`ename`,`tel`,`gender`,`salary`,`commission_pct` commissionPct ,`birthday`,`hiredate`,`job_id` jobId,`email`,`mid`,`address`,`native_place` nativePlace,`did` from t_employee where eid=?";
  39. Employee emp = null;
  40. try {
  41. emp = getBean(Employee.class, sql, eid);
  42. } catch (Exception e) {
  43. throw new RuntimeException(e);
  44. }
  45. return emp;
  46. }
  47. @Override
  48. public List<Employee> getAll() {
  49. //这里sql中通过取别名的方式,来指定对应的Javabean的属性名
  50. String sql = "select `eid`,`ename`,`tel`,`gender`,`salary`,`commission_pct` commissionPct ,`birthday`,`hiredate`,`job_id` jobId,`email`,`mid`,`address`,`native_place` nativePlace,`did` from t_employee";
  51. List<Employee> list = new ArrayList<Employee>();
  52. try {
  53. list = getList(Employee.class, sql);
  54. } catch (Exception e) {
  55. throw new RuntimeException(e);
  56. }
  57. return list;
  58. }
  59. @Override
  60. public long empCount() {
  61. String sql = "select count(1) from t_employee";
  62. Long count = 0L;
  63. try {
  64. Object obj = getObject(sql);
  65. count = (Long) obj;
  66. } catch (Exception e) {
  67. throw new RuntimeException(e);
  68. }
  69. return count;
  70. }
  71. @Override
  72. public double avgSalary() {
  73. String sql = "select avg(salary) from t_employee";
  74. Double avg = 0.0;
  75. try {
  76. avg = (Double) getObject(sql);
  77. } catch (Exception e) {
  78. throw new RuntimeException(e);
  79. }
  80. return avg;
  81. }
  82. @Override
  83. public Map<Integer, Double> avgSalaryPerDepartment() {
  84. String sql = "select did,avg(salary) from t_employee group by did";
  85. Map<Integer, Double> map = new HashMap<>();
  86. try {
  87. List<Map<String, Object>> mapList = getMapList(sql);
  88. /*
  89. * String:字段的名称,例如:did,avg(salary)
  90. * Object:字段的值,例如1,19819.408666666666
  91. * {did=1, avg(salary)=19819.408666666666}
  92. {did=2, avg(salary)=11708.5}
  93. {did=3, avg(salary)=70223.0}
  94. {did=4, avg(salary)=12332.0}
  95. {did=5, avg(salary)=11065.5}
  96. */
  97. for (Map<String, Object> map2 : mapList) {
  98. map.put((Integer)map2.get("did"),(Double)map2.get("avg(salary)"));
  99. }
  100. } catch (Exception e) {
  101. throw new RuntimeException(e);
  102. }
  103. return map;
  104. }
  105. }

示例代码:

  1. package com.atguigu.test09.dbutil;
  2. import java.util.List;
  3. import java.util.Map;
  4. import org.junit.Test;
  5. import com.atguigu.bean.Employee;
  6. public class TestEmployeeDAOImp2 {
  7. @Test
  8. public void test1(){
  9. EmployeeDAOImpl2 ed2 = new EmployeeDAOImpl2();
  10. List<Employee> all = ed2.getAll();
  11. for (Employee employee : all) {
  12. System.out.println(employee);
  13. }
  14. }
  15. @Test
  16. public void test2(){
  17. EmployeeDAOImpl2 ed2 = new EmployeeDAOImpl2();
  18. long count = ed2.empCount();
  19. System.out.println(count);
  20. }
  21. @Test
  22. public void test3(){
  23. EmployeeDAOImpl2 ed2 = new EmployeeDAOImpl2();
  24. double avgSalary = ed2.avgSalary();
  25. System.out.println(avgSalary);
  26. }
  27. @Test
  28. public void test4(){
  29. EmployeeDAOImpl2 ed2 = new EmployeeDAOImpl2();
  30. Map<Integer, Double> map = ed2.avgSalaryPerDepartment();
  31. map.forEach((k,v) -> System.out.println(k+"->"+v));
  32. }
  33. }

经典错误

1、jar包版本不兼容

  1. Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
  2. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  3. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  4. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  5. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  6. at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
  7. at com.mysql.jdbc.Util.getInstance(Util.java:383)
  8. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1023)
  9. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:997)
  10. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:983)
  11. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:928)
  12. at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2576)
  13. at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2309)
  14. at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:834)
  15. at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:46)
  16. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  17. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  18. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  19. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  20. at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
  21. at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:419)
  22. at com.mysql.jdbc.NonRegisteringDriver.connect(Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
  23. Exception in thread "main" java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
  24. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
  25. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
  26. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
  27. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
  28. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
  29. at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:76)
  30. at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835)
  31. at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:455)
  32. at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240)
  33. at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:199)
  34. at java.sql.DriverManager.getConnection(DriverManager.java:664)
  35. at java.sql.DriverManager.getConnection(DriverManager.java:247)
  36. .java:344)
  37. at java.sql.DriverManager.getConnection(DriverManager.java:664)
  38. at java.sql.DriverManager.getConnection(DriverManager.java:247)

看异常好像是无事务连接异常,无法创建连接。将MySQL驱动改为了最新的8.0版本的MySQL驱动。显示那个驱动类已经过时了,新的驱动类是“com.mysql.cj.jdbc.Driver”,而不是“com.mysql.jdbc.Driver”了,并且还说我没有配置时区,查了一下,原来从JDBC6.0开始驱动类使用了新的,并且url中必须要设置时区,否侧会报错。

  1. 第一步:使用最新的MySQL驱动jar包。
  2. 第二步:把驱动的类名改为:
  3. static String driver="com.mysql.cj.jdbc.Driver";
  4. 第三步:在访问mysqlurl后加入时区设置:
  5. static String url="jdbc:mysql://localhost:3306/test "

?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai**

2.单元测试不能使用Scanner

JDBC笔记 - 图6

  1. -Deditable.java.test.console=true

如果还不行