一、JDBC概述
1、JDBC概述
JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。
2、JDBC使用步骤
package com.liufei.jdbc;
import org.junit.Test;
import java.sql.*;
/**
* JDBC的使用步骤
* 1、注册驱动。Class.forName("驱动的全限定名");com.mysql.jdbc.Driver。如果使用的是mysql8,那么应该使用的是mysql8的驱动
* jar包,驱动的全限定名是com.mysql.cj.jdbc
* 2、获得连接(建立客户端与mysql服务器的连接)
* Connection conn = DriverManager.getConnection("数据库服务器路径名","用户名","密码");
* 3、创建执行sql语句的Statement对象
* Statement statement = conn.createStatement();
* 4、使用statement执行sql语句
* 增删改的:int num = statement.executeUpdate("sql");返回值表示受到影响的行数
* 查询: ResultSet resultSet = statement.executeQuery("sql");返回值是查询到的结果集
* 5、如果第四步执行的是查询操作,那么我们就要将查询到的结果集中的数据遍历出来(难点)
* 6、关闭资源 后创建的先关闭
* resultSet.close();
* statement.close();
* conn.close();
*
*/
public class TestJdbc {
@Test
public void test01() throws Exception {
//目标:使用JDBC执行查询所有用户的sql语句
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获得连接 "jdbc:mysql://服务器的主机地址:端口号/数据库名?characterEncoding = utf8
// 如果数据库主机地址是localhost,并且端口号是3306,那么可以省略localhost:3306
String url = "jdbc:mysql://localhost:3306/day04?characterEncoding=utf8";
String user = "root";
String password = "666666";
Connection conn = DriverManager.getConnection(url,user,password);
//3、创建statement对象
Statement statement = conn.createStatement();
//4、使用statement对象执行sql语句
String sql = "select* from user";
//int num = statement.executeUpdate("");
//执行查询数据的sql语句,获得查询到的结果集
ResultSet resultSet = statement.executeQuery(sql);
//5、遍历resultSet,从中获取到查询的数据
while(resultSet.next()){
int id = (int) resultSet.getObject("id");
String username = (String) resultSet.getObject("username");
String psw = (String) resultSet.getObject("password");
String nickname = (String) resultSet.getObject("nickname");
System.out.println(id + ":" + username + ":" + psw + ":" + nickname);
System.out.println("-----------------");
}
//6、关闭资源,后创建的先关闭
resultSet.close();
statement.close();
conn.close();
}
}
3、JDBC的增删改查练习
public class TestJdbc {
private Statement statement;
private Connection conn;
@Before
public void init() throws Exception {
//目标:使用JDBC执行查询所有用户的sql语句
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获得连接 "jdbc:mysql://服务器的主机地址:端口号/数据库名?characterEncoding = utf8
// 如果数据库主机地址是localhost,并且端口号是3306,那么可以省略localhost:3306
String url = "jdbc:mysql://localhost:3306/day04?characterEncoding=utf8";
String user = "root";
String password = "666666";
conn = DriverManager.getConnection(url,user,password);
//3、创建statement对象
statement = conn.createStatement();
}
@Test
public void test01() throws Exception {
//4、使用statement对象执行sql语句
String sql = "select* from user";
//int num = statement.executeUpdate("");
//执行查询数据的sql语句,获得查询到的结果集
ResultSet resultSet = statement.executeQuery(sql);
//5、遍历resultSet,从中获取到查询的数据
while(resultSet.next()){
int id = (int) resultSet.getObject("id");
String username = (String) resultSet.getObject("username");
String psw = (String) resultSet.getObject("password");
String nickname = (String) resultSet.getObject("nickname");
System.out.println(id + ":" + username + ":" + psw + ":" + nickname);
System.out.println("-----------------");
}
//6、关闭资源,后创建的先关闭
resultSet.close();
}
@After
public void destory() throws Exception {
statement.close();
conn.close();
}
@Test
public void testInsert() throws Exception {
//测试执行添加数据的语句
String sql = "insert into user (username,password,nickname) values ('tq','77777','田七')";
int i = statement.executeUpdate(sql);
}
@Test
public void testDelete() throws Exception {
//删除id为4的用户
String sql = "delete from user where id = 4";
int i = statement.executeUpdate(sql);
}
@Test
public void testUpdate() throws Exception {
//将id为三的用户密码改为88888888
String sql = "update user set password = 88888888 where id = 3";
int i = statement.executeUpdate(sql);
}
@Test
public void testFindByid() throws Exception {
//查询id为1的用户信息
String sql = "select* from user where id = 1";
ResultSet resultSet = statement.executeQuery(sql);
//遍历结果集
while (resultSet.next()){
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String nickname = resultSet.getString("nickname");
//从数据库查询出来数据的目的是:为了在java代码的内存中操作查询出来的数据
//我们要将查询出来的一行数据作为一个整体:就将查询出来的这行数据中的各列数据存储到一个map或者user对象中
User user = new User(id, username, password, nickname);
System.out.println(user);
}
}
}
二、使用PreparedStatement处理CRUD
1、Statement存在的问题
1.1 每次执行一个SQL语句都需要先编译
String sql1 = "insert into user values(null,'tq','77777','田七')";
String sql2 = "insert into user values(null,'zl','666666','赵六')";
String sql3 = "insert into user values(null,'zs','333333','张三')";
//如果使用Statement执行上述SQL语句需要编译三次
1.2 sql注入
@Test
public void testError() throws SQLException {
//演示sql注入的问题
String username = "zsf' or '1'='1";
String password = "123456";
//根据username和password查询用户,其实就是模拟登录
String sql = "select* from user where username = '"+username+"' and password = '"+password+"'";
ResultSet resultSet = statement.executeQuery(sql);
//我们只需要判断resultSet里面有么有内容
if (resultSet.next()) {
System.out.println("登录成功了。。。");
}else{
System.out.println("登录失败了...");
}
}
2、PreparedStatement解决问题
package com.liufei.jdbc;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.sql.*;
public class TestPrepareStatement {
private Connection conn;
@Before
public void init() throws Exception {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获得连接
conn = DriverManager.getConnection("jdbc:mysql:///day04?characterEncoding=utf8", "root", "666666");
}
@Test
public void testLogin() throws Exception {
//3、预编译化的参数语句
String username = "zs";
String password = "123456";
//3.1 编写参数化的sql语句,也就是说需要传入参数的地方使用?占位
String sql = "select* from user where username=? and password=?";
//3.2预编译sql语句:可以确定sql语句的结构,那么预编译之后就无法通过sql注入改变sql语句的结构了
PreparedStatement preparedStatement = conn.prepareStatement(sql);
//4、给?占位符传入对应的参数
preparedStatement.setObject(1,username);
preparedStatement.setObject(2,password);
//5、执行sql语句,此时就不用在传入sql语句了,因为在预编译的时候已经传过了
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()){
System.out.println("登录成功。。。");
}else{
System.out.println("登陆失败。。。");
}
resultSet.close();
preparedStatement.close();
}
@After
public void destory() throws Exception {
conn.close();
}
@Test
public void testInsert() throws Exception {
String sql = "insert into user(username,password,nickname) values(?,?,?)";
//预编译sql语句
PreparedStatement preparedStatement = conn.prepareStatement(sql);
//设置占位符的参数
preparedStatement.setObject(1,"zl");
preparedStatement.setObject(2,"666666");
preparedStatement.setObject(3,"老赵");
//执行sql语句
int i = preparedStatement.executeUpdate();
System.out.println(i);
preparedStatement.close();
}
}
3、获取自增长键值
3.1 获取自增长键值的应用场景
主要使用在一些复杂的业务中,在添加完主表的一条数据之后,要获取到这条数据的主键值,然后将该值添加进从表的外键字段
3.2 获取自增长键值的步骤
-
在预编译的时候,指定要返回自增长的key
PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
-
在执行完添加数据的SQL语句之后,通过PreparedStatement的对象调用getGeneratedKeys()方法来获取自增长键值,遍历结果集
ResultSet rs = pst.getGeneratedKeys();
-
遍历获取自增长的键值
if(rs.next()){ Object key = rs.getObject(1); System.out.println("自增的key值did =" + key); }
示例代码
@Test
public void testObtainPkyAfterInsert() throws Exception {
//测试在添加数据之后获取主键值
String sql = "insert into user(username,password,nickname) values(?,?,?)";
//预编译的时候,就要指定不仅要预编译,还要获取自增长的主键值
PreparedStatement preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//设置sql语句的参数
preparedStatement.setObject(1,"aobama");
preparedStatement.setObject(2,"666666");
preparedStatement.setObject(3,"神枪游侠");
//执行sql语句
int i = preparedStatement.executeUpdate();
System.out.println("添加了" + i + "行数据");
//单独获取自增长的主键值
ResultSet rst = preparedStatement.getGeneratedKeys();
//因为主键只有一个,所以可以不遍历
if (rst.next()) {
//表示获取到了自增长的主键,我们就取出主键值
int id = rst.getInt(1);
System.out.println("自增长的主键是:" + id );
}
}
4、批处理
4.1 批处理优势和应用场景
批处理相比较单独一条条执行SQL语句来说,其效率高很多。批处理一般会使用在批量添加多条数据和批量修改多条数据
4.2 批处理的具体操作步骤
-
在url中要加一个参数
rewriteBatchedStatements=true
,那么此时url就变成了jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
-
在完成所有参数设置之后,调用PreparedStatement的addBatch()方法,添加到批处理中
-
最后执行PreparedStatement的executeBatch()方法执行批处理语句
5、事务
5.1 事务操作的步骤
- 执行逻辑单元之前先开启事务
- 逻辑单元执行完毕,没有出现异常则提交事务
- 逻辑单元执行过程中出现异常,则回滚事务
5.2 事务相关API
Connection中与事务有关的方法 | 说明 |
---|---|
setAutoCommit(boolean autoCommit) | 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction; |
void commit() | 提交事务; 类似sql里面的 commit; |
void rollback() | 回滚事务; 类似sql里面的 rollback; |
5.3 使用JDBC的事务完成转账案例
5.3.1 准备数据
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values (null,'zs',1000);
insert into account values (null,'ls',1000);
insert into account values (null,'ww',1000);
5.3.2 代码实现
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestTransaction {
@Test
public void testTransfer() throws Exception {
//测试转账
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql:///day04?characterEncoding=utf8", "root", "666666");
//预编译sql语句
String sql = "update account set money=money+? where name=?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
//开启事务
conn.setAutoCommit(false);
try {
//zs 扣款500
preparedStatement.setObject(1,-500);
preparedStatement.setObject(2,"zs");
//执行扣款的sql语句
preparedStatement.executeUpdate();
int num = 10 / 0;
//lz 收款500
preparedStatement.setObject(1,500);
preparedStatement.setObject(2,"lz");
preparedStatement.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
} finally {
conn.setAutoCommit(true);
}
//关闭资源
preparedStatement.close();
conn.close();
}
}
三、数据库连接池
1、什么是数据库连池
连接池是connection对象的缓冲区,它里面会存放一些connection,当我们Java程序需要使用connection的时候,如果连接池中有则直接从连接池获取,不需要去新创建connection了。连接池让Java程序能够复用连接、管理连接
3.2 为什么要使用连接池
- 1.因为每次创建和销毁连接都会带来较大的系统开销
- 2.每次创建和销毁连接都要消耗大概0.05~1s的时间。
- 3.可以防止大量用户并发访问数据库服务器。
3.3 连接池的优势
- 资源重用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
- 更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
- 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年前也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
- 统一的连接管理,避免数据库连接泄漏
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。
3.5 连接池的原理
- 连接池维护着两个容器空闲池和活动池
- 空闲池用于存放未使用的连接,活动池用于存放正在使用的连接,活动池中的连接使用完之后要归还回空闲池
- 当Java程序需要连接时,先判断空闲池中是否有连接,如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用
- Java程序需要连接时,如果空闲池中没有连接了,则先判断活动池的连接数是否已经达到了最大连接数,如果未达到最大连接数,则会新创建一个连接放置到活动池,供Java程序使用
- 如果空闲池中没有连接了,活动池中的连接也已经达到了最大连接数,则不能新创建连接了,那么此时会判断是否等待超时,如果没有等待超时则需要等待活动池中的连接归还回空闲池
- 如果等待超时了,则可以采取多种处理方式,例如:直接抛出超时异常,或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用
3.6 连接池的实现
3.6.1 DataSource接口
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),所有的Java数据库连接池都需要实现该接口。该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现
3.6.2 常见的数据库连接池
- DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- HikariCP 俗称光连接池,是目前速度最快的连接池
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池
3.6.3 Druid连接池的使用
(1)加入jar包
例如:druid-1.1.10.jar
(2)代码步骤
第一步:创建druid连接池的配置文件druid.properties文件,放置到类路径下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day04?characterEncoding=utf8
username=root
password=123456
#初始化连接数
initialSize=5
#最大活动连接数
maxActive=10
#最大等待时间
maxWait=1000
第二步:使用工厂模式创建DruidDataSource对象
//1. 创建一个Properties对象,让其去读取druid.properties文件
Properties properties = new Properties();
//1.1 将druid.properties配置文件转成字节输入流
//FileInputStream is = new FileInputStream("D:\\IdeaProject2019\\JDBC\\resources\\druid.properties");
//使用相对路径来将配置文件转成字节输入流,我们可以使用类加载器来读取类路径下文件
//TestDataSource.class.getClassLoader() 表示获取ClassLoader对象
InputStream is = TestDataSource.class.getClassLoader().getResourceAsStream("druid.properties");
//1.2 使用properties对象加载流
properties.load(is);
第三步:使用连接池对象获取连接
//从连接池取出一个连接使用
Connection connection = dataSource.getConnection();
3.6.4 Druid连接池的配置参数列表
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的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,是组合关系,并非 |
3.7 封装JDBCTools
package com.liufei.jdbc.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*
这个工具类中会提供仨方法:
* 1. 获取连接池对象
* 2. 从连接池中获取连接
* 3. 将链接归还到连接池
* */
public class JDBCTools {
private static DataSource dataSource;
static {
try {
//1. 使用类加载器读取配置文件,转成字节输入流
InputStream is = JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties");
//2. 使用Properties对象加载字节输入流
Properties properties = new Properties();
properties.load(is);
//3. 使用DruidDataSourceFactory创建连接池对象
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接池对象
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
/**
* 获取连接
* @return
*/
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
public static void releaseConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
连接池使用的总结
- 拷贝加入druid的jar包
- 拷贝druid的配置文件到类路径,并修改
- 拷贝JDBCTools工具类
- 在需要连接的地方编写
Connection conn = JDBCTools.getConnection();
此时拿到的连接就是从连接池拿的 - 连接使用完毕之后,调用
JDBCTools.releaseConnection(conn);
归还连接
四、 Apache的DBUtils
4.1 DBUtils的概述
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
其中QueryRunner类封装了SQL的执行,是线程安全的。
(1)可以实现增、删、改、查、批处理、
(2)考虑了事务处理需要共用Connection。
(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
4.2 DBUtils执行增删改的SQL语句
4.2.1 API介绍
- QueryRunner() ,创建QueryRunner对象,用于执行SQL语句
- QueryRunner的update(Connection conn, String sql, Object… params)方法,用于执行增删改的SQL语句
4.2.2 代码实现
package com.liufei.jdbc;
import com.liufei.jdbc.utils.JDBCTools;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Before;
import org.junit.Test;
import java.sql.SQLException;
public class TestDBUtils {
private QueryRunner queryRunner;
@Before
public void init(){
queryRunner = new QueryRunner(JDBCTools.getDataSource());
}
@Test
public void testInsert() throws Exception {
// //往user表中添加一行数据
// //第一种方式:需要自己在执行sql语句的时候手动传入连接对象
// //1、创建QueryRunner对象
// QueryRunner queryRunner = new QueryRunner();
// //2、调用queryRunner对象的方法执行sql语句,如果是调用增删改的语句则调用update()方法
// String sql = "insert into user (username,password,nickname) values (?,?,?)";
// queryRunner.update(JDBCTools.getConnection(),sql,"aobama","666666","奥巴马");
//第二种方式:直接将连接池对象交给QueryRunner
//QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
String sql = "insert into user (username,password,nickname) values (?,?,?)";
queryRunner.update(sql,"lifei","666666","李飞");
}
@Test
public void testDelete() throws Exception {
String sql = "delete from user where id=?";
queryRunner.update(sql,99);
}
@Test
public void testUpdate() throws Exception {
String sql = "update user set nickname=? where id=?";
queryRunner.update(sql,"赵刚",5);
}
}
4.3 DBUtils执行批处理
4.3.1 API介绍
public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException
: 支持批处理INSERT, UPDATE, or DELETE语句public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object[][] params)throws SQLException
:只支持INSERT语句
4.3.2 代码实现
@Test
public void testBatched() throws Exception {
//测试批量添加,编写sql语句
String sql = "insert into user (username,password,nickname) values (?,?,?)";
//创建一个二维数组,用于存储批量参数,二维数组的第一维表示批量操作多少条数据,第二维表示每条数据要设置多少个参数
Object[][] parms = new Object[20][3];
//循环设置批量添加的数据
for(int i = 0;i < 20;i++){
parms[i][0] = "葫芦娃_"+i;
parms[i][1] = "55555_"+i;
parms[i][2] = "精钢葫芦娃_"+i;
}
//执行批量操作
queryRunner.batch(sql,parms);
}
4.4 使用QueryRunner类实现查询
4.4.1 API介绍
- query(String sql, ResultSetHandler rsh, Object… params) ,执行查询 select
- ResultSetHandler结果集处理类
Handler类型 | 说明 |
---|---|
ArrayHandler | 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值 |
ArrayListHandler | 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。 |
BeanHandler | 将结果集中第一条记录封装到一个指定的javaBean中。 |
BeanListHandler | 将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中 |
ColumnListHandler | 将结果集中指定的列的字段值,封装到一个List集合中 |
KeyedHandler | 将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值。 |
MapHandler | 将结果集中第一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值 |
MapListHandler | 将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中。 |
ScalarHandler | 它是用于单个数据。例如select count(*) from 表。 |
4.4.2 代码实现
@Test
public void testFindByid() throws Exception {
String sql = "select * from user where id=?";
//queryRunner执行查询的sql语句,调用query方法
User user = queryRunner.query(sql, new BeanHandler<>(User.class), 1);
System.out.println(user);
}
@Test
public void testFindMore() throws SQLException {
//查询id<10 的所有数据
String sql = "select *from user where id <?";
List<User> userlist = queryRunner.query(sql, new BeanListHandler<>(User.class),10);
System.out.println(userlist);
}
@Test
public void testFindCount() throws SQLException {
//目标查询用户总数
String sql = "select count(*) from user";
long count = queryRunner.query(sql, new ScalarHandler<>());
System.out.println(count);
}
经典错误
1、jar包版本不兼容
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
at com.mysql.jdbc.Util.getInstance(Util.java:383)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1023)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:997)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:983)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:928)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2576)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2309)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:834)
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:46)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:419)
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.
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.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:76)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:455)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:199)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:247)
.java:344)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:247)
看异常好像是无事务连接异常,无法创建连接。将MySQL驱动改为了最新的8.0版本的MySQL驱动。显示那个驱动类已经过时了,新的驱动类是“com.mysql.cj.jdbc.Driver”,而不是“com.mysql.jdbc.Driver”了,并且还说没有配置时区,查了一下,原来从JDBC6.0开始驱动类使用了新的,并且url中必须要设置时区,否侧会报错。
第一步:使用最新的MySQL驱动jar包。
第二步:把驱动的类名改为:
static String driver="com.mysql.cj.jdbc.Driver";
第三步:在访问mysql的url后加入时区设置:
static String url="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC"