需求: 查询student表的所有数据,把数据封装到一个集合中
#创建表
CREATE TABLE student(
sid INT,
name VARCHAR(100),
age INT,
sex VARCHAR(100)
)
#插入数据
INSERT INTO student VALUES(1,'张三',18,'女'),(2,'李四',19,'男'),(3,'王五',20,'女'),(4,'赵六',21,'男')
连接池配置文件内容:
driverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/day05pre
username = root
password = root
initialSize = 5
maxActive = 10
minIdle = 3
maxWait = 60000
德鲁伊连接池工具类
import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class DruidUtil { //1.创建一个连接池对象 private static DataSource dataSource; //静态代码创建,这样第一次使用这个类的时候就可以直接创建DataSource对象了 static{ try { //读取Druid.properties文件中的数据 创建连接池对象 InputStream is = DruidUtil.class.getClassLoader().getResourceAsStream("Druid.properties"); //创建properties集合载入流中数据 Properties pro = new Properties(); pro.load(is); //Druid工具载入pro集合中的数据 创建数据源对象 dataSource = DruidDataSourceFactory.createDataSource(pro); } catch (Exception e) { e.printStackTrace(); } } //2.创建方法 返回一个连接 public static Connection getConn() throws SQLException{ return dataSource.getConnection(); } //3.关闭所有资源 public static void closeAll(ResultSet rs, PreparedStatement pst, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (pst != null) { try { pst.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } |
---|
这个类的字段与数据库字段对应
public class Student { private int sid; private String name; private int age; private String sex; 构造 set/get toString } |
---|
/* * 查询student类中的所有的信息 展示到控制台上 * * */ @Test public void selectAll() throws Exception{ //1.获取链接 Connection conn = DruidUtil.getConn(); System.out.println("Demo04 ======>selectAll() ======> 获取链接完毕 conn= "+conn); //2.通过链接获取SQL的发射器 String sql = "select * from student"; PreparedStatement pst = conn.prepareStatement(sql); //3.发射SQL语句 得到结果集 ResultSet rs = pst.executeQuery(); System.out.println("Demo04 ======>selectAll() ======> 发射完毕 rs= "+rs); //创建一个集合 List<Student> list = new ArrayList<Student>(); //4.处理结果集 while(rs.next()){ Student s = new Student(rs.getInt("sid"), rs.getString("name"), rs.getInt("age"), rs.getString("sex")); list.add(s); } //5.关闭资源 DruidUtil.closeAll(rs, pst, conn); //遍历 for(Student stu: list){ System.out.println(stu); } } |
---|
完成转账功能
创建数据表和数据
# 创建账号表
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
money DOUBLE
);
# 初始化数据
INSERT INTO account VALUES (NULL,'张三',10);
INSERT INTO account VALUES (NULL,'李四',10);
创建两个方法分别实现取钱和存钱
import java.sql.Connection; import java.sql.PreparedStatement; import com.czxy.util.DruidUtil; public class AccountDao { /* * 从指定的账户中取钱(减钱) * */ public void outMoney(String name,int money) throws Exception{ //1.获取链接 Connection conn = DruidUtil.getConn(); //2.发射器 String sql = "UPDATE account SET money=money-? WHERE NAME=?"; PreparedStatement pst = conn.prepareStatement(sql); pst.setInt(1, money); pst.setString(2, name); //3.发射 int num = pst.executeUpdate(); //4.处理结果 System.out.println("本次执行的影响的行数是: num="+num); //5.关闭资源 DruidUtil.closeAll(null, pst, conn); } /* * 从指定的账户中存钱(加钱) * */ public void inMoney(String name,int money) throws Exception{ //1.获取链接 Connection conn = DruidUtil.getConn(); //2.发射器 String sql = "UPDATE account SET money=money+? WHERE NAME=?"; PreparedStatement pst = conn.prepareStatement(sql); pst.setInt(1, money); pst.setString(2, name); //3.发射 int num = pst.executeUpdate(); //4.处理结果 System.out.println("本次执行的影响的行数是: num="+num); //5.关闭资源 DruidUtil.closeAll(null, pst, conn); } } |
---|
创建一个方法完成转账业务
import com.czxy.dao.AccountDao; public class AccountService { /** * 实现转账 * @param srcName : 钱的来源 * @param descName : 钱的去向 * @param money : 钱数 * */ public void transfer(String srcName,String descName,int money){ AccountDao ad = new AccountDao(); try { // -钱 ad.outMoney(srcName, money); // +钱 ad.inMoney(descName, money); System.out.println("转账完毕 "); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
---|
创建一个测试类 ,书写main方法 实现转账
public class Demo02 { public static void main(String[] args) { //创建Service对象 AccountService as = new AccountService(); //张三 给 李四 转2块钱 as.transfer("张三", "李四", 2); } } |
---|
执行前:
执行后:
如果转账的中间出现了bug,很容易导致A账户的钱减少了,但是B账户的钱没有增加。这会造成事故,不是我们期望看到的。
下面代码在Service层模拟转账过程出现问题。
效果如下:
转账前:
执行代码:
转账出错结果:
让转账的两个动作:减钱,加钱 必须同时成功或者是同时失败。
数据库的事务。
sql语句 | 描述 |
---|---|
start transaction; | 开启事务 |
commit; | 提交事务(完整更新) |
rollback; | 回滚事务(恢复原状) |
start transaction;
update account set money=money-1000 where name='守义';
update account set money=money+1000 where name='凤儿';
commit;
#或者
rollback;
show variables like '%commit%';
* 设置自动提交的参数为OFF:
set autocommit = 0; -- 0:OFF 1:ON
START TRANSACTION; -- 开启事物
-- 执行一组操作
UPDATE account SET money=money-1 WHERE NAME='张三';
UPDATE account SET money=money+1 WHERE NAME='李四';
COMMIT; -- 提交事物
执行前:
执行后:
# 测试利用事物实现转账1块钱
START TRANSACTION; -- 开启事物
-- 执行一组操作
UPDATE account SET money=money-1 WHERE NAME='张三';
-- 下一句发生错误
UPDATE account SET money=money+1 WHERE NAME &……&%&……¥(*&* ='李四';
ROLLBACK; -- 回滚(恢复原状)
执行前:
执行如下两句:
执行效果:
执行下面一句
这一组操作的第二个给李四加钱执行失败了 ,李四的钱并不会改变
此时一组操作没有全部成功,需要回滚来让数据恢复原状
Connection对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 设置关闭自动提交,(开启事务) |
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
利用如下模板解决问题
//事务模板代码
public void demo01() throws SQLException{
// 获得连接
Connection conn = ...;
try {
//#1关闭自动提交事物(开始事务)
conn.setAutoCommit(false);
//.... 加钱 ,减钱
//#2 手动提交事务
conn.commit();
} catch (Exception e) {
//#3 手动回滚事务
conn.rollback();
} finally{
// 释放资源
conn.close();
}
}
把原来的两个方法进行修改,使用Service层传递过来的conn对象,并且执行完毕不要关闭链接
/* * 从指定的账户中取钱(减钱) * */ public void outMoney(String name,int money,Connection conn) throws Exception{ //2.发射器 String sql = "UPDATE account SET money=money-? WHERE NAME=?"; PreparedStatement pst = conn.prepareStatement(sql); pst.setInt(1, money); pst.setString(2, name); //3.发射 int num = pst.executeUpdate(); //4.处理结果 System.out.println("本次执行的影响的行数是: num="+num); //5.关闭资源 只关闭结果集不关闭链接 DruidUtil.closeAll(null, pst, null); } /* * 从指定的账户中存钱(加钱) * */ public void inMoney(String name,int money,Connection conn) throws Exception{ //2.发射器 String sql = "UPDATE account SET money=money+? WHERE NAME=?"; PreparedStatement pst = conn.prepareStatement(sql); pst.setInt(1, money); pst.setString(2, name); //3.发射 int num = pst.executeUpdate(); //4.处理结果 System.out.println("本次执行的影响的行数是: num="+num); //5.关闭资源 只关闭结果集不关闭链接 DruidUtil.closeAll(null, pst, null); } |
---|
获取连接,关闭自动提交变成手动提交,一组动作成功则手动提交事务,一旦有异常则回滚,最后无论异常与否都要关闭连接
public void transfer(String srcName,String descName,int money){ AccountDao ad = new AccountDao(); Connection conn =null; try { //获取链接 conn = DruidUtil.getConn(); //把自动提交关闭,变成手动提交 conn.setAutoCommit(false); // -钱 传递连接对象 ad.outMoney(srcName, money,conn); //制造一个bug , 模拟转账出现问题 int a=1/0; // +钱 传递连接对象 ad.inMoney(descName, money,conn); //转账成功则手动提交事物 conn.commit(); System.out.println("转账完毕 "); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); if(conn!=null){ try { //操作失败 回滚 conn.rollback(); System.out.println("执行了回滚 ,把数据恢复原状 "); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }finally { if(conn!=null){ try { //关闭链接 conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } |
---|
把 如下代码注释上
执行前:
转账2块钱执行完毕
执行结果
保留如下代码
执行前:
执行效果
执行后:数据恢复原状,问题解决
如果不考虑隔离性,事务存在3种并发访问问题。
严重性: 脏读 > 不可重复读 >虚读(幻读)
效率最高,引发所有读问题
基本不设置
如果要 效率,那么选择这个read committed
如果 要求安全,选择这个repeatable read
虚读的问题可以通过程序来规避:
没有效率,安全性最高,基本不设置