事务&数据库连接池&DBUtil
事务
Transaction 其实是指一组操作,里卖弄包含许多个单一的逻辑,只要有一个逻辑没有子hi习惯成功,那么就算失败,所有的数据就都回归到最初的状态。
使用命令行的方式演示事务
- 开启事务
start transaction
- 提交或回滚事务
- commit : 提交事务
- rollback : 回滚事务
1.关闭自动提交功能
2.演示事务
使用代码演示事务
代码里面的事务,主要是针对连接
- 步骤
- 1.通过conn.setAutoCommit(flase)来关闭自动提交的设置
- 2.提交事务 conn.commit();
- 3.回滚事务 conn.rollback();
- 代码
@Test
public void testTransaction(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtil.getConnection();
//默认的连接,事务是自动提交的
//关闭自动提交
conn.setAutoCommit(false);
//转账事务
String sql = "update account set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
//扣钱 ,扣id为1的用户100块
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
//加钱,给id为2的用户加100元
ps.setInt(1, -100);
ps.setInt(2, 2);
ps.executeUpdate();
//成功:提交事务
conn.commit();
} catch (SQLException e) {
try {
//失败: 回滚事务
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtil.close(rs, ps, conn);
}
}
事物的特性
- 原子性
指的是 事务中包含的逻辑,不可分割
- 一致性
指的是 事务在执行前后,数据的完整性
- 隔离性
指的是 事务在执行期间不应该受到其他事务的影响
- 持久性
指的是 事务执行成功,那么数据应该持久保存在磁盘上
事务的安全隐患
不考虑隔离级别的设置,那么会出现以下问题:
- 读
- 脏读
一个事务读到另一个事务还没提交的数据
- 不可重复读
一个事务读到了另一个事务提交的数据,造成了前后两次查询结果
- 幻读
一个事务读到另一个事务insert的数据,造成前后查询结果的不一致
- 脏读
- 写
- 丢失更新
隔离级别
mysql默认的隔离级别是 可重复读
- Read Uncommitted【读未提交】
- Read Committed 【读已提交】
- Repeatable 【重复读】
- Serializable 【可串行化】
1.读未提交的演示
1.设置A窗口的隔离级别为 读未提交
2.两个窗口都分别开启事务
2.读已提交的演示
1.设置隔离级别为 读已提交
2.A B 两个窗口都开启事务,在B窗口执行更新操作
3.在A窗口执行的查询结果不一致,一次是在B窗口提交事务之前,一次是在B窗口提交事务之后
这个隔离级别能够屏蔽脏读的现象,但是也引发了另一个问题,不可复读。
3.可串行化
如果有一个连接的隔离级别设置为可串行化,那么谁先打开了事务,谁就有了先执行的权利,谁后打开事务,谁就只能等之前的事务提交或回滚后,才能执行。
但是这种隔离级别容易造成性能上的问题,效率较低。
- 按效率分,从高到低
读未提交 > 读已提交 > 可重复读 > 可串行化
- 按拦截程度
可串行化 > 可重复读 > 读已提交 > 读未提交
丢失更新
- 悲观锁
可以在查询的时候,加入 for update
- 乐观锁
需要程序员自己控制
数据库连接池
一开始先在内存中开辟一块空间(集合),一开始先网池子里放置多个连接对象。当需要连接的时候,直接俄从池子里取,使用完毕后,归还连接,确保连接对象能循环利用。
自定义连接池
开源连接池
DBCP
1.导入jar文件
commons-dbcp.jar
commons-pool.jar
2.不使用配置文件
@Test
public void testJDBC() {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.构建数据库连接对象
BasicDataSource dataSource = new BasicDataSource();
/*
* 连接数据库类型,访问的数据库、用户名、密码
* jdbc:mysql://localhost/test 主协议:子协议:// 本地 /数据库
*/
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
//2.得到连接对象
conn = dataSource.getConnection();
String sql = "insert into account value(3,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "wangwu");
ps.setInt(2, 1000);
ps.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3.使用配置文件
- 配置文件dbcpconfig.properties
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/test
username=root
password=
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=utf8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=REPEATABLE_READ
- 连接数据库
@Test
public void testJDBC() {
Connection conn = null;
PreparedStatement ps = null;
try {
BasicDataSourceFactory factory = new BasicDataSourceFactory();
Properties properties = new Properties();
InputStream is;
is = new FileInputStream("src//dbcpconfig.properties");
properties.load(is);
DataSource dataSource = factory.createDataSource(properties);
// 2.得到连接对象
conn = dataSource.getConnection();
String sql = "insert into account value(4,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "zhaoliu");
ps.setInt(2, 1000);
ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
C3P0
1.导入jar文件
c3p0-0.9.1.2.jar
2.不使用配置文件
@Test
public void testJDBC() {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.创建dataSource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2.设置来捏数据的信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost/test");
dataSource.setUser("root");
dataSource.setPassword("root");
// 2.得到连接对象
conn = dataSource.getConnection();
String sql = "insert into account value(5,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "hhhhh");
ps.setInt(2, 400);
ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3.使用配置文件
- 配置文件
c3p0-config.xml
,放在classpath中,或classes目录中(放在工程的src文件下即可)
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
<user-overrides user="test-user">
<property name="maxPoolSize">10</property>
<property name="minPoolSize">1</property>
<property name="maxStatements">0</property>
</user-overrides>
</default-config>
</c3p0-config>
- 创建连接
@Test
public void testJDBC() {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.创建dataSource
//默认会找配置文件c3p0-config.xml中的default-config分支
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 2.得到连接对象
conn = dataSource.getConnection();
String sql = "insert into account value(6,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "wwww");
ps.setInt(2, 400);
ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
DBUtil
基本用法
1.增删改
//dbUtils:只是帮助简化了CRUD的代码,但是连接的创建以及获取工作,不在作用范围内
//创建连接
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//update(): 更新,包括插入、删除、修改
//插入
queryRunner.update("insert into account values(6,?,?)", "aa",2333);
//删除
queryRunner.update("delete from account where id = ?", 6);
//修改
queryRunner.update("update account set money = ? where id = 1", 1);
2.查询
- 直接new接口的匿名实现类
//query(): 查询,去执行查询,查询到的数据还是在那个result里面,然后调用下面的handle方法,有用户去封装
Account account = queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>(){
public Account handle(ResultSet rs) throws SQLException {
Account account = new Account();
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int money = rs.getInt("money");
account.setId(id);
account.setName(name);
account.setMoney(money);
}
return account;
}
},1);
- 直接使用框架中的实现类
- 查询单个对象
/* * 通过字节码得到该类的实例 * Account a = new Account(); * * Account a2 = Account.class.newInstance(); */ //查询单个对象 Account account2 = queryRunner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class),2); //打印内容 System.out.println(account2.toString());
- 查询多个对象
//查询对象集合 List<Account> list = queryRunner.query("select * from account",new BeanListHandler<Account>(Account.class)); //打印内容 for(Account account3: list) { System.out.println(account3.toString());
常用实现类
- BeanHeadler : 查询到的单个数据封装成一个对象
- BeanListHandler : 查询到的单个数据封装成一个List<对象>
- ArrayHandler : 查询到的单个数据封装成一个数组
- ArrayListHandler : 查询到的单个数据封装成一个集合,集合里面的元素是数组
- MapHandler : 查询到的单个数据封装成一个map
- MapListHandler : 查询到的单个数据封装成一个集合,集合里面的元素是map
- ColumnListHandler
- KeyedHandler
- ScalarHandler