JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(Database Management System, DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。
即JDBC技术包含两个部分:
1)java.sql包和javax.sql包中的API
因为为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。
2)各个数据库厂商提供的jar
因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
create database jdbc_test;
use jdbc_test;
create table user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
nickname varchar(20)
);
INSERT INTO `USER` VALUES(null,'zs','123456','老张');
INSERT INTO `USER` VALUES(null,'ls','123456','老李');
INSERT INTO `USER` VALUES(null,'wangwu','123','老王');
1)将DBMS数据库管理软件的驱动jar拷贝到项目的lib目录中
根据不同的数据库版本进行驱动的选择
2)把驱动jar添加到项目的build path中,即将jar包放入在src目录下新建的lib目录下,然后右键lib目录选择add as library
3)将驱动类加载到内存中
Class.forName("com.mysql.jdbc.Driver");
如果是8版本则将驱动改为:
Class.forName("com.mysql.cj.jdbc.Driver");
1)Connection conn = DriverManager.getConnection(url,username,password);
2)mysql的url:jdbc:mysql://localhost:3306/数据库名?参数名=参数值
String url = "jdbc:mysql://localhost:3306/数据库名?参数名=参数值&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
如果用户使用了 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥;可以在连接中通过 ServerRSAPublicKeyFile 指定服务器的 RSA 公钥,或者AllowPublicKeyRetrieval=True参数以允许客户端从服务器获取公钥;但是需要注意的是 AllowPublicKeyRetrieval=True可能会导致恶意的代理通过中间人攻击(MITM)获取到明文密码,所以默认是关闭的,必须显式开启
修改Timezone使MySQL的timezone和idea的一致后可以不写&serverTimezone=UTC&allowPublicKeyRetrieval=true
3)jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
String url = "jdbc:mysql://localhost:3306/jdbc_testuseUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "root";
//获得连接
Connection connection = DriverManager.getConnection(url, user, password);
编写sql
String sql = "select *from user";
创建Statement对象
Statement statement = connection.createStatement();
使用Statement对象执行sql
1)增删改:调用executeUpate方法
2)查询:调用executeQuery方法
ResultSet resultSet = statement.executeQuery(sql);
处理结果
1)增删改:返回的是整数值,表示受到影响的数据条数
2)查询:返回ResultSet结果
while (resultSet.next()) {
//获取每一列的数据
System.out.println(resultSet.getObject(1));
System.out.println(resultSet.getObject(2));
System.out.println(resultSet.getObject(3));
System.out.println(resultSet.getObject(4));
}
原则是后创建的资源先关闭,我们会依次关闭ResultSet、Statement、Connection对象
//关闭资源
if(resultSet != null){
resultSet.close();
}
if(statement != null){
statement .close();
}
if(connection != null){
connection.close();
}
IDEA/数据库连接
@Test
public void insert() throws Exception {
//增加 insert into user values(null,'tq','77777','田七');
//1.注册驱动
// Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获得连接
String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
//3.创建执行sql语句对象
Statement statement = connection.createStatement();
//4.执行sql语句
String sql = "insert into user values(null,'tq','77777','田七')";
int rows = statement.executeUpdate(sql);
System.out.println("几行收影响=" + rows);
//5.释放资源
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
@Test
//删除id为5的用户
public void delete() throws Exception {
//1.注册驱动
// Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获得连接
String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
//3.创建执行sql语句对象
Statement statement = connection.createStatement();
//4.执行sql语句
String sql = "delete from user where id = 5";
statement.executeUpdate(sql);
//5.释放资源
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
@Test
//更新 把id为4的用户的密码改成88888888
public void update() throws Exception {
//1.注册驱动
//Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获得连接
String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
//3.创建执行sql语句对象
Statement statement = connection.createStatement();
//4.执行sql语句
String sql = "update user set password = '88888888' where id = 4";
statement.executeUpdate(sql);
//5.释放资源
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
要求: 将查询到的结果封装到User对象中
User类
public class User {
private int id;
private String username;
private String password;
private String nickname;
//提供get/set方法 Alt+Insert
public User() {
}
public User(int id, String username, String password, String nickname) {
this.id = id;
this.username = username;
this.password = password;
this.nickname = nickname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
JDBC 代码
@Test
public void query() throws Exception {
//查询id为1的用户
//1.注册驱动
//Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获得连接
String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
String username = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, username, password);
//3.创建执行sql语句对象
Statement statement = connection.createStatement();
//4.执行sql语句
String sql = "select * from user where id = 1";
ResultSet resultSet = statement.executeQuery(sql);
User user = null;
while (resultSet.next()) {
//每遍历一次,就是一条数据.就是一个User对象(有数据才有user)
user = new User(resultSet.getInt("id"),
resultSet.getString("username"),
resultSet.getString("password"),
resultSet.getString("nickname"));
}
//获得用户名
System.out.println("用户名="+user.getUsername());
//5.释放资源
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
要求: 将查询到的多行数据封装到List
@Test
//查询所有用户
public void queryAll() throws Exception {
//1.注册驱动
//DriverManager.registerDriver(new Driver());
//类全限定名(带包名), 加载Driver类, 静态代码块就会执行, 驱动就注册了
//Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获得连接(连接数据库)
//连接数据库路径
String url="jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
String username = "root";
String password= "root";
Connection connection = DriverManager.getConnection(url, username, password);
//3.创建执行sql语句的对象
Statement statement = connection.createStatement();
//4.执行sql语句, 处理结果
String sql = "select * from user";
ResultSet resultSet = statement.executeQuery(sql);
List<User> list = new ArrayList<User>();
while (resultSet.next()){
//每遍历一次就是一条数据, 就封装成一个User对象. 把封装的每一个User添加到list集合里面
User user = new User(resultSet.getInt("id"),
resultSet.getString("username"),
resultSet.getString("password"),
resultSet.getString("nickname")
);
list.add(user);
}
//获得第二个用户的用户名
System.out.println(list.get(1));
//5.释放资源(先创建的后关闭)
if(resultSet != null){
resultSet.close();
}
if(statement != null){
statement.close();
}
if(connection != null){
connection.close();
}
}
每次执行一个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语句需要编译三次
sql注入漏洞
String username = "hahahahha' or '1'='1"
String sql = "SELECT * FROM user where username='" + username + "'";
//结果会把所有数据都查询出来
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
预编译: PreparedStatement会先对参数化的SQL语句进行预编译,执行SQL语句的时候不会再进行编译
String sql = "insert into user values(null,?,?,?)";
//预编译
PreparedStatement pstm = connection.prepareStatement(sql);
//后续设置参数、执行添加多少条数据都不会再重新编译
防止SQL注入: PreparedStatement在进行预编译的时候,就已经确定好了SQL语句的格式,不会再因为后续的操作改变SQL语句的格式
String username = "hahahahha' or '1'='1"
String sql = "SELECT * FROM user where username=?";
//即使输入'张三' or '1'= '1'也没问题
PreparedStatement pst = conn.prepareStatement(sql);
//中间加入设置?的值
pst.setObject(1, username);
ResultSet rs = pst.executeQuery();
主要使用在一些复杂的业务中,在添加完主键表的一条数据之后,要获取到这条数据的主键值,然后将该值添加进外键表的外键字段。
步骤一:在预编译的时候,指定要返回自增长的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);
}
示例代码
public class TestAutoIncrement {
public static void main(String[] args) throws Exception{
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8", "root", "123456");
//3、执行sql
String sql = "insert into user values(null,?,?,?)";
/*
* 这里在创建PreparedStatement对象时,传入第二个参数的作用,就是告知服务器端
* 当执行完sql后,把自增的key值返回来。返回给了pst,通过pst.getGeneratedKeys();
* 可以获取自增的key值
*/
PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//设置?的值
pst.setObject(1, "aobama");
pst.setObject(2, "12345678");
pst.setObject(3, "圣枪游侠");
//执行sql
int len = pst.executeUpdate();//返回影响的记录数
if(len>0){
//从pst中获取到服务器端返回的键值
ResultSet rs = pst.getGeneratedKeys();
//因为这里的key值可能多个,因为insert语句可以同时添加多行,所以用ResultSet封装
//这里因为只添加一条,所以用if判断
if(rs.next()){
Object key = rs.getObject(1);
System.out.println("自增的key值did =" + key);
}
}
//4、关闭
pst.close();
conn.close();
}
}
批处理相比较单独一条条执行SQL语句来说,其效率高很多。批处理一般会使用在批量添加多条数据和批量修改多条数据。
步骤一:在url中要加一个参数 rewriteBatchedStatements=true
,那么此时url就变成了
jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
步骤二:在完成所有参数设置之后,调用PreparedStatement的addBatch()方法,添加到批处理中
步骤三:最后执行PreparedStatement的executeBatch()方法执行批处理语句
public class TestBatch {
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
//例如:在部门表t_department中添加1000条模拟数据
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true", "root", "123456");
//3、执行sql,sql语句不能用value,只能用values
String sql = "insert into user values(null,?,?,?)";
PreparedStatement pst = conn.prepareStatement(sql);
//设置?的值
for (int i = 1; i <=1000; i++) {
pst.setObject(1, "aobama"+i);
pst.setObject(2, "000000"+i);
pst.setObject(3, "圣枪游侠"+i);
pst.addBatch();//添加到批处理一组操作中,攒一块处理
}
pst.executeBatch();
//4、关闭
pst.close();
conn.close();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));//耗时:821
}
}
1)执行逻辑单元之前先开启事务
2)逻辑单元执行完毕,没有出现异常则提交事务
3)逻辑单元执行过程中出现异常,则回滚事务
Connection中与事务有关的方法 | 说明 |
---|---|
setAutoCommit(boolean autoCommit) | 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction; |
void commit() | 提交事务; 类似sql里面的 commit; |
void rollback() | 回滚事务; 类似sql里面的 rollback; |
事务中最经典的案例就是转账操作,如果A向B转了100块,在A转出了后系统出错了,B没有收到,那么A就平白无故少了100块钱了,这是非常严重的错误。这时候就需要引入事务对转账操作进行处理。
事务的四大特性如下:
原子性:一个事务是一个不可分割的工作单位,事务中包括的所有操作要么都做,要么都不做。
一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性:持久性也称永久性,指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
所以转账是一个事务,也就具备了以上的四个特性,具备的原子性可以保证A转出100块后,B要收到100块,这个转账操作才算完成,如果B收不到100块,那么就将A转出的100块退回给他,之所以能够退回是因为这些操作都记录到了数据库中的日志表中,当转账失败事务执行回退时,也就是撤销该转账操作时,日志表中的该条操作就执行一个逆操作,增—>删,加—>减等。引入的事务操作主要就是保证原子性,其他特性数据库帮我们完成了,下面看下具体的代码逻辑。
use jdbc_test;
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);
package com.atguigu.jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class TestTransaction {
@Test
public void testTransfer() throws Exception {
//测试转账
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql:///day04?characterEncoding=utf8","root","123456");
//3. 预编译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");
//执行zs扣款的sql语句
preparedStatement.executeUpdate();
//ls收款500
preparedStatement.setObject(1,500);
preparedStatement.setObject(2,"ls");
preparedStatement.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
}finally {
//还原connection的AutoCommit为true
conn.setAutoCommit(true);
}
//关闭资源
preparedStatement.close();
conn.close();
}
}
保证原子性的关键操作就是使用一个try…catch…
将转账操作进行包裹,当抛出异常时执行回退逻辑,也就是撤销之前事务中执行的全部操作流程。如果没有异常则提交事务,将SQL操作所产生的影响进行持久化操作。
什么是连接池?
连接池是connection对象的缓冲区,它里面会存放一些connection,当我们Java程序需要使用connection的时候,如果连接池中有则直接从连接池获取,不需要去新创建connection了。连接池让Java程序能够复用连接、管理连接。
为什么要使用连接池?
1)因为每次创建和销毁连接都会带来较大的系统开销
2)每次创建和销毁连接都要消耗大概0.05~1s的时间。
3)可以防止大量用户并发访问数据库服务器。
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),所有的Java数据库连接池都需要实现该接口。该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现。
第一步:加入jar包(druid-1.1.10.jar)
第二步:创建druid连接池的配置文件druid.properties文件,放置到类路径下
类路径是什么?
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?
username=root
password=123456
initialSize=5
maxActive=10
maxWait=1000
第三步:在项目创建一个工具包utils,在里面新建JDBCUtil工具类:
package com.drimwai.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;
public class JDBCUtil {
private static DataSource dataSource;
static {
try {
//读取配置文件,创建连接池
//1. 创建一个Properties对象,让其去读取druid.properties文件
Properties properties = new Properties();
//2. 使用相对路径来将druid.properties配置文件转成字节输入流,我们可以使用类加载器来读取类路径下文件
//JDBCUtil.class.getClassLoader()表示获取ClassLoader对象
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");
//使用properties对象加载流
properties.load(inputStream);
//使用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());
}
}
/**
* 归还连接的方法
* @param connection
*/
public static void releaseConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
配置 | 缺省 | 说明 |
---|---|---|
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,是组合关系,并非替换关系 |
Connection conn = JDBCUtil.getConnection();
此时拿到的连接就是从连接池拿的JDBCUtil.releaseConnection(conn);
归还连接JDBCUtil.getDataSource();
获取连接池本节的DBUtil是由Apache所提供的jar包实现了对数据库增删改查操作,而第六节的BaseDao是由我们基于DBUtil自定义的用于实现通用的数据库增删改查操作。上面的JDBCUtils只是实现了数据库连接池的创建和连接的获取、释放等操作,并没有实现对数据的操作,而DBUtils和BaseDao就是实现对数据的操作的,但他们没有实现连接的获取以及连接池的创建等操作,所以要使用apache的DBUtil或者BaseDao都要先实现上面的Druid连接池的使用。
Apache的DBUtils的使用
第一步:先根据上面的Druid连接池的使用创建JDBCUtils工具包,主要是要用到JDBCUtils里创建的Connection连接
第三步:创建一个类,用以下方法便可实现Apache的DBUtils的使用的增删改操作,下面有查功能,这里不赘述。
//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "delete from user where id=?";
queryRunner.update(sql,1);
commons-dbutils是 Apache 组织提供的一个开源 JDBC工具框架(类库),它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
其中QueryRunner类封装了SQL的执行,是线程安全的。
(1)可以实现增、删、改、查、批处理、
(2)考虑了事务处理需要共用Connection。
(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
@Test
public void testAddUser() throws SQLException {
//目标:往user表中添加一行数据
//1. 创建QueryRunner需先导入上面提到的jar包
QueryRunner queryRunner = new QueryRunner();
//2. 执行SQL语句
String sql = "insert into user values (null,?,?,?)";
Connection conn = JDBCUtil.getConnection();
int i = queryRunner.update(conn, sql, "aolafu", "123456", "狂战士");
//关闭连接
JDBCUtil.releaseConnection(conn);
}
@Test
public void testAddUserAnother() throws SQLException {
//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "insert into user values (null,?,?,?)";
queryRunner.update(sql,"neisesi","123456","狗头");
//这种方式的缺点是用不了事务
}
@Test
public void testDeleteUser() throws SQLException {
//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "delete from user where id=?";
queryRunner.update(sql,1);
}
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语句@Test
public void testBatch() throws SQLException {
//使用DBUtils执行批处理
//二维数组:数组内的元素还是数组{{1,2},{3,4},{5,6}}
//批量往user表中添加五千条数据
//1. 创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 编写SQL语句
String sql = "insert into user (username,password,nickname) values (?,?,?)";
//3. 设置二维数组参数:第一维表示我们需要处理多少条数据,第二维表示sql语句有多少个参数
Object[][] params = new Object[5000][3];
for(int i=0;i<5000;i++){
//params[i]表示插入的第i条数据
//params[i][1]表示插入的第i条数据的第一个问号处的参数
params[i][0] = "username"+i;
params[i][1] = "password"+i;
params[i][2] = "nickname"+i;
}
//执行批处理
queryRunner.batch(sql,params);
}
Handler类型 | 说明 |
---|---|
ArrayHandler | 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值 |
ArrayListHandler | 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。 |
BeanHandler | 将结果集中第一条记录封装到一个指定的javaBean中。 |
BeanListHandler | 将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中 |
ColumnListHandler | 将结果集中指定的列的字段值,封装到一个List集合中 |
KeyedHandler | 将结果集中每一条记录封装到Map |
MapHandler | 将结果集中第一条记录封装到了Map |
MapListHandler | 将结果集中每一条记录封装到了Map |
ScalarHandler | 它是用于单个数据。例如select count(*) from 表。 |
@Test
public void testFindById() throws SQLException {
//使用DBUtils执行查询的SQL语句,将查询到的一条数据封装到User对象中
//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "select * from user where id=?";
User user = queryRunner.query(sql, new BeanHandler<>(User.class), 2);
System.out.println(user);
}
@Test
public void testFindAll() throws SQLException {
//查询多条数据,封装到List
//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "select * from user";
List<User> userList = queryRunner.query(sql, new BeanListHandler<>(User.class));
System.out.println(userList);
}
@Test
public void testFindCount() throws SQLException {
//查询数据条数
//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "select count(id) from user";
long count = queryRunner.query(sql, new ScalarHandler<>());
System.out.println(count);
}
Dao是data access Object的缩写,中文翻译为数据访问对象,我们会将操作持久层的代码编写到对应的Dao类中。
BaseDao类:(所有Dao类的父类)需要先实现上面的Druid连接池的使用,注意BaseDao是基于DBUtils自定义实现的,所以BaseDao是需要依赖DBUtils的,所以也需要引入对应的apache的jar包。
BaseDao实现了通用的数据库增删改查操作。
import com.atguigu.bookstore.utils.JDBCUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class BaseDao<T> {
private QueryRunner queryRunner = new QueryRunner();
/**
* 批处理方法
* @param sql
* @param paramArr
* @return
*/
public int[] batchUpdate(String sql,Object[][] paramArr){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.batch(conn,sql,paramArr);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
/**
* 执行增删改的sql语句
* @param sql
* @param params
* @return
*/
public int update(String sql,Object... params){
Connection conn = JDBCUtil.getConnection();
//执行增删改的sql语句,返回受到影响的行数
try {
return queryRunner.update(conn,sql,params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
/**
* 执行查询一行数据的sql语句,将结果集封装到JavaBean对象中
* @param clazz
* @param sql
* @param params
* @return
*/
public T getBean(Class<T> clazz,String sql,Object... params){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.query(conn,sql,new BeanHandler<>(clazz),params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
/**
* 执行查询多行数据的sql语句,并且将结果集封装到List
* @param clazz
* @param sql
* @param params
* @return
*/
public List<T> getBeanList(Class<T> clazz, String sql, Object... params){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.query(conn,sql,new BeanListHandler<>(clazz),params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
}
测试代码如下:
@Test
public void testUpdate() {
BaseDao<Object> baseDao = new BaseDao<>();
String sql = "insert into t_user(user_name,user_pwd) values(?,?)";
int count = baseDao.update(sql, "罗志祥", "789456");
System.out.println("count = " + count);
}
@Test
public void testGetBean() {
//这里的User表示数据类型,之前已经创建了一个User类了,用来存放用户的 id ,username ,password, nickname
BaseDao<User> baseDao = new BaseDao<>();
// user_id userId
// user_name userName
// user_pwd userPwd
String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user where user_id=?";
User user = baseDao.getBean(User.class, sql, 2);
System.out.println("user = " + user);
}
@Test
public void testGetBeanList() {
//这里的User表示数据类型,之前已经创建了一个User类了,用来存放用户的 id ,username ,password, nickname
BaseDao<User> baseDao = new BaseDao<>();
String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user";
List<User> userList = baseDao.getBeanList(User.class, sql);
for (User user : userList) {
System.out.println("user = " + user);
}
}