事物
事物的基本介绍
总结数据库:
执行不会提交
- 当自动提交关闭时,执行成功SQL语句,不会把数据提交到数据库
-
默认执行就提交
课堂练习
没有事务
//操作转账的业务
//1.获得连接
Connection connection = null;
//2.组织SQ语句
String sql = "update account set balance = balance + ? where name = ?";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();//默认情况下,connection是默认自动提交
preparedStatement = connection.prepareStatement(sql);
//给?赋值
preparedStatement.setDouble(1,-100);
preparedStatement.setString(2,"马云");
//执行
preparedStatement.executeUpdate();//执行第一条
int i = 1 / 0;//抛出异常
//马化腾
//给?赋值
preparedStatement.setDouble(1,100);
preparedStatement.setString(2,"马化腾");
//执行
preparedStatement.executeUpdate();//执行第二条
} catch (SQLException e) {
e.printStackTrace();
}
//关闭资源
有事务
try {
connection = JDBCUtils.getConnection();//默认情况下,connection是默认自动提交
//将connection 设置为手动提交
connection.setAutoCommit(false);//相当于开启了事务
preparedStatement = connection.prepareStatement(sql);
//给?赋值,//第一条
preparedStatement.setDouble(1,-100);
preparedStatement.setString(2,"马云");
//执行
preparedStatement.executeUpdate();
//给?赋值, //第二条
preparedStatement.setDouble(1,100);
preparedStatement.setString(2,"马化腾");
//执行
preparedStatement.executeUpdate();
connection.commit(); //提交事物
} catch (SQLException e) {
//事务回滚,即撤销执行的SQL
//默认回滚事务开始的状态
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
}
//关闭资源
总结事务
try抛出异常之后,catch捕获异常,因此这里进行回滚rollback()
- 事务回滚,即撤销执行的SQL;默认回滚事务开始的状态
commit() 写在java执行sql后面;即catch之前,excuteUpdate之后
2 批处理
2.1基本介绍
2.2批处理总结:
优点:
-
注意:
jdbc配置文件加url后加参数 ?rewriteBatchedStatements=true
- addBatch()装好之后,然后executeBacth()再执行,最后clearBatch()清空;
发车顺序:(装数据,执行,清空)
就像发车一样,来回拉不同的客人,preparedStatement来回执行不同的数据
2.3 课堂练习
演示向admin表中添加5000条数据
注意:jdbc配置文件加url后加参数 ?rewriteBatchedStatements=true
CREATE TABLE admin3( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL, PASSWORD VARCHAR(32) NOT NULL);
a. 传统方式
//连接
String sql = "insert into admin2 values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1,"jack" + i);
preparedStatement.setString(2,"666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统方式耗时=" + (end - start));//传统方式耗时=17939
//关闭连接
b. 批处理方式
//连接
String sql = "insert into admin3 values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1,"Andy" + i);
preparedStatement.setString(2,"123456");
//1.将sql语句加入到批处理包中
preparedStatement.addBatch();
//当有1000条记录时,在批量执行
if((i + 1) % 1000 == 0){//满1000条sql
//2.批量执行
preparedStatement.executeBatch();
//3.清空1000条sql
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("批量处理方式耗时=" + (end -start));//批量处理方式耗时=178
//关闭连接
2.4 源码分析
将sql语句放到批处理包中—>源码分析(preparedStatement.addBatch())
- 第一次创建Arraylist - elementDate =>Obejct[]
- elementDate =>Obejct[]就会存放我们预处理的sql语句
- 当 elementDate满后,就按照1.5扩容
- 当添加到指定的值后,就会executeBatch
- 批量处理减少发生sql语句的网络开销,且减少了编译次数,因此效率提高
概括总结:addBatch()会数据放到一个集合里面,等到了指定值之后,就执行executeBatch
3 数据库的连接池-解决连接复用问题
3.1 问题引出
测试
原因分析连接:
- 连接池放入指定数量的连接,通过网络跟数据库形成了通道
- 程序从连接池取出连接
- 使用完连接之后,放回连接池
-
3.3 连接池原理图:
3.4 数据库连接池种类
数据库连接池总结
3.5 C3P0 引用实例
连接池代码体现是DataSource(数据源),连接和管理交给数据源了,以后就是数据源帮连接
因此数据源需要知道(user、password、driver以及url)
初始化连接:程序运行创建好的
最大连接:当程序连接超过初始化连接,会创建新的连接,最多所有连接不能超过最大连接
等待队列:当程序连接超过最大连接,会进入等待队列,等待其他连接释放
comboPooledDataSource.setInitialPoolSize(10);//程序运行之后连接池放10个连接//方式1:相关参数,在程序中指定user,url,password等
public void testC3P0_01() throws Exception {
//1.创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2.通过配置文件获取相关信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关属性值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//给数据源comboPooledDataSource设置相关参数
//注意:连接管理由comboPooledDataSource来管理的
comboPooledDataSource.setDriverClass(driver);//连接数据库的驱动
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//初始化数据源连接数
//初始化连接:程序运行创建好的
//最大连接:当程序连接超过初始化连接,会创建新的连接,最多所有连接不能超过最大连接
//等待队列:当程序连接超过最大连接,会进入等待队列,等待其他连接释放
comboPooledDataSource.setInitialPoolSize(10);//程序运行之后连接池放10个连接
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);
//测试连接池的效率,测试连接mysql5000次
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
//这个是实现DataSource接口的方法
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("C3P0 5000连接,耗时" + (end - start));//C3P0 5000连接,耗时2300
}
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hsp_edu");
//测试连接池的效率,测试连接mysql5000次
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("C3P0的第二种方式:5000连接,耗时" + (end - start));
//C3P0的第二种方式:5000连接,耗时2157
修改配置文件c3p0-config.xml
url、driver、user、password
最大连接,最小连接,初始化连接、每次增长连接、每个连接最多执行的语句
3.5 DRUID 引用实例
数据源是帮忙管理的数据库的连接,因此需要驱动,仅仅在上面做了包装做了缓冲池而已
配置文件
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/hsp_db02?rewriteBatchedStatements=true
username=root
password=root
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=50
#max wait time (5000 mil seconds)
maxWait=5000
maxWait=5000
等待队列:超过5秒还没有取到连接,放弃等待
解决方案:调整initialSize和MaxActive
//1.加入Druid jar包
//2.加入配置文件,将文件拷贝项目的src目录
//3.创建properties对象,读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//4.创建一个指定参数的数据库连接池,Druid的连接池
//连接池创建根据配置文件properties的参数作为依据
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = dataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("druid连接池 操作500000次,耗时=" + (end - start));
//druid连接池 操作500000次,耗时=3517
mysql数据库厂商原生和Druid是Alibaba第三方实现
- 前后者实现java接口的方式不同,因此两者实现类是不同的(动态绑定)
- 前者的connection是创建连接,而后者是取到连接
前者close是真正关闭连接,后者是把连接引用断掉,连接放到数据源中
把Druid的配置文件和连接、关闭改为工具类
工具类
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成ds初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//编写getConnection方法
public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭连接,强调:在数据库连接池技术中,close不是真正的断掉连接
//而是把使用的Connection对象放回连接池
public static void close(ResultSet resultSet, Statement statement,Connection connection){
//判断是否为null
try {
if(resultSet != null){
resultSet.close();
}
if(statement != null){
statement.close();
}
if(connection != null){
connection.close();
}
} catch (SQLException e) {
//将编译异常转成运行异常抛出
throw new RuntimeException(e);
}
}
}
工具类总结:
工具类必要项——静态代码块:配置文件读取和创建数据源
多处使用的部分——静态属性:数据源(传入配置文件参数到druid的数据工厂获得一个数据源)
分离的部分——静态方法:connection( )和close( )使用工具类
//1.得到连接
Connection connection = null;
//2.组织sql
String sql = "select * from actor where id >= ?";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtilsByDruid.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,4);
//执行,得到结果集
resultSet = preparedStatement.executeQuery();
//遍历结果集
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String gender = resultSet.getString("gender");
Date birthday = resultSet.getDate("birthday");
String cellphone = resultSet.getString("cellphone");
System.out.println(id + "\t" + name + "\t" + gender + "\t" + birthday + "\t" + cellphone);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtilsByDruid.close(resultSet,preparedStatement,connection);
}
4.Apache-DBUtils-解决结果集复用和增删改查问题
4.1 分析一个问题
分析问题:
- resultSet要connectin一直处于连接状态
- 其他处不能调用ressultSet
4.2 原理图
Actor类把mysql的字段封装到属性中 ```java //核心代码 //创建ArrayList对象,存放Actor ArrayListlist = new ArrayList<>();
//把得到的resultSet的记录,封装到Actor对象,放入到list集合 list.add(new Actor(id,name,gender,birthday,cellphone));
//调用者直接调用方法得mysql集合复用
public ArrayList
}
<a name="DQZUF"></a>
### 封装到集合优势:
1. 可复用:把字段存放=>Actor类中,然后把Actor对象=>存放到集合中 可复用
1. 方法返回集合:可以把方法void改为返回集合,这样调用者可直接得到数据集合
1. 依赖性变得更差:断掉connection,resultSet不影响,已经把数据封装到集合中了,集合跟连接是没有关系的
<a name="wKPzd"></a>
## 4.3 Apache-DBUtils
<a name="I0S2d"></a>
## ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12555714/1621769438099-52a373ce-6b37-4ab5-acd4-7f772fe1fab1.png#clientId=u3c30609c-2470-4&from=paste&height=727&id=u1769c121&name=image.png&originHeight=727&originWidth=1341&originalType=binary&size=1510990&status=done&style=none&taskId=u153ee74d-f31e-4000-82d8-02c85b21b1d&width=1341)
<a name="NrRx6"></a>
#### jar包[commons-dbutils-1.3.jar](https://www.yuque.com/attachments/yuque/0/2021/jar/12555714/1621837842778-2fd7d256-83c1-458f-8576-f42880595b11.jar?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fwww.yuque.com%2Fattachments%2Fyuque%2F0%2F2021%2Fjar%2F12555714%2F1621837842778-2fd7d256-83c1-458f-8576-f42880595b11.jar%22%2C%22name%22%3A%22commons-dbutils-1.3.jar%22%2C%22size%22%3A41376%2C%22type%22%3A%22%22%2C%22ext%22%3A%22jar%22%2C%22status%22%3A%22done%22%2C%22taskId%22%3A%22ud339c9ce-515a-4680-92e2-6d19946b9c8%22%2C%22taskType%22%3A%22upload%22%2C%22id%22%3A%22u7f42bd5c%22%2C%22card%22%3A%22file%22%7D)
<a name="ktiD3"></a>
## 4.4 课堂练习
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12555714/1621769832580-f1af10e4-b1bc-40aa-a5d3-5a71d261f173.png#clientId=u3c30609c-2470-4&from=paste&height=193&id=ue95e934c&name=image.png&originHeight=193&originWidth=522&originalType=binary&size=135435&status=done&style=none&taskId=uc5aa3fe5-251b-4c77-b7c3-57f43652bd5&width=522)
<a name="YXUCq"></a>
### 代码实现:
<a name="fDvNv"></a>
#### 多行实现
```java
//演示apache-dbutils + druid完成返回结果是多行记录
public void testQueryMany() throws SQLException {//返回结果是多行的情况
//1.得到连接(druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2.使用DBUtils类和接口,先引入DBUtils相关jar
//3.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4.可以执行相关的方法,返回Arraylist结果集
String sql = "select * from actor where id >= ?";
//说明:query()
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
for(Actor actor : list){
System.out.println(actor);
}
JDBCUtilsByDruid.close(null,null,connection);
}
单行实现
//演示apache-dbutils + druid完成返回结果是单行记录(单个记录)
核心代码
//因为返回单个记录--->单个记录,使用Handler是BeanHandler
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class),4);
//释放连接
单行单列
//演示 apache-dbutils + druid 完成查询结果是单行单列-返回就是object
//核心代码
//因为返回的是一个对象,使用handler就是ScalarHandler
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
dml
//演示 apache-dbutils + druid 完成dml
//执行相关方法,返回单个对象
String sql = "update actor set name = ? where id = ?";
//1.执行dml操作是queryRunner.update()
//2.返回值是受影响的行数
int affectRow = queryRunner.update(connection, sql, "刘德华", 8);
System.out.println(affectRow > 0 ? "执行成功" : "执行没有影响到表");
String sql = "insert into actor values(null,?,?,?,?)";
int affectRow = queryRunner.update(connection, sql, "赵敏","女","2000-10-10","17984559847");
String sql = "delete from actor where id = ?";
int affectRow = queryRunner.update(connection, sql, 4);
4.5 总结说明:
- query() 执行sql语句,得到resultSet—-封装到—->ArrayList集合中
- 返回集合
- connection:连接
- sql:执行sql语句
- new BeanListHandler<>(Actor.class):在resultSet—>Actor对象—>封装到ArrayList
底层使用反射机制去获取Actor类的属性,然后进行封装
- 1就是给sql语句中的?赋值,可以有多个值,因为是可变餐宿Object…params
- 底层得到resultSet,会在query() 关闭,还会关闭PreparedStatement
4.6 源码分析:
4.7 表和JavaBean的类型映射关系
mysql列类型和java数据类型总结:
- 数值型:基本类型用包装类,因为msql的所有类型都有可能是null
- 字符串:mysql中的char,java要用String
日期型:mysql中的date,java可以用date或String
5.Dao-解决灵活使用sql
5.1 先分析一个问题
5.2 原理图
5.3 基本说明
5.4 应用实例
代码实现
utils====>JDBCUtilsByDruid apach-dbutils
javabean====>Actor
DAO====>ActorDAO继承BasicDAO,BasicDAO是其他DAO的父类,ActorDAO根据业务需求写特有的功能(演示为空)
test====>创建ActorDAO对象,因为ActorDAO继承BasicDAO,所以可以直接使用BasicDAO的增删改查BasicDAO
// apach-dbutils
public class BasicDAO<T> {//泛型指定具体类型
private QueryRunner qr = new QueryRunner();
//开发通用dml方法,针对任意的表
public int update(String sql,Object... params) throws Exception {
Connection connection = JDBCUtilsByDruid.getConnection();
int rows = qr.update(connection, sql, params);
return rows;
JDBCUtilsByDruid.close(null, null, connection);
}
//查询多行记录
public List<T> queryMultiple(String sql,Class<T> clazz,Object... params){
Connection connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanListHandler<T>(clazz), params);
JDBCUtilsByDruid.close(null, null, connection);
}
//查询单行结果的通用方法
public T querySingle(String sql,Class<T> clazz,Object... params) {
Connection connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(clazz), params);
JDBCUtilsByDruid.close(null, null, connection);
}
//查询单行单列结果的通用方法
public Object queryScalar(String sql,Object... params){
Connection connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql,new ScalarHandler(), params);
JDBCUtilsByDruid.close(null, null, connection);
}
}
5.4 课后练习