连接池-DBUtils-事务
1.学习目标:a.利用原生的JDBC完成对数据库的增删改查b.利用预处理对象完成对数据库的增删改查c.会使用C3P0连接池以及Druid连接池
第一章.PreparedStatement预处理对象
1.用读取配置文件的方式编写工具类
在src下创建jdbc.properties文件:driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/day04_37?characterEncoding=utf8username=rootpassword=root
public class JDBCUtils2 {
private static String driverClassName;
private static String url;
private static String username;
private static String password;
//注册驱动,初始化url,username,password是最先初始化的,所以放在static代码块中
static{
//注册驱动
try {
//创建Properties集合
Properties properties = new Properties();
//利用字节输入流读取配置文件
InputStream in = JDBCUtils2.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
//获取Properties集合中的数据
driverClassName = properties.getProperty("driverClassName");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
Class.forName(driverClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
Connection connection = null;
try {
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//关闭资源
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public class Demo06JDBC_PreparedStatement {
public static void main(String[] args) throws Exception {
//获取连接
Connection conn = JDBCUtils2.getConn();
//准备sql
String sql = "select * from user";
//获取PreparedStatement对象
PreparedStatement pst = conn.prepareStatement(sql);
//由于没有占位符,我们查询的是所有,所以不用为占位符赋值了
//执行sql
ResultSet resultSet = pst.executeQuery();
//处理结果集
while(resultSet.next()){
Object id = resultSet.getObject("id");
Object username = resultSet.getObject("username");
Object password = resultSet.getObject("password");
System.out.println(id+"..."+username+"..."+password);
}
//释放资源
JDBCUtils2.close(conn,pst,resultSet);
}
}
2.mysql批量添加数据
修改properties文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day04_37?characterEncoding=utf8&rewriteBatchedStatements=true
username=root
password=root
1.在设置完所有要添加的参数,调用PreparedStatement中的addBatch(),将SQL语句添加到PreparedStatement中
2.调用PreparedStatement中的executeBatch()方法批处理sql语句
public class Test01 {
@Test
public void insert()throws Exception{
//获取连接对象
Connection connection = JDBCUtils.getConnection();
String sql = "insert into user (username,password) values (?,?)";
//获取执行平台
PreparedStatement pst = connection.prepareStatement(sql);
//批量添加
for (int i = 0; i < 1000; i++) {
pst.setObject(1,"名字"+i);
pst.setObject(2,"密码"+i);
pst.addBatch();
}
//执行sql
int[] arr = pst.executeBatch();
for (int i : arr) {
System.out.println(i);
}
//关闭资源
JDBCUtils.close(null,pst,connection);
}
}
第二章.连接池
1.连接池之C3p0
1.连接池:容器,里面存有很多的连接对象,需要的话去连接池中拿连接对象,用完还回去
2.java为连接池提供了一个标准,接口:javax.sql.DataSource,不同的厂商如果要想实现自己的连接池就要实现这个DataSource接口
3.常用的连接池:C3p0 DRUID
1.导jar包:c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar
2.在src下面创建c3p0-config.xml->名字不能错
3.在xml中配置相关信息:
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day05?characterEncoding=utf8</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">10</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</default-config>
</c3p0-config>
初始连接数(initialPoolSize):刚创建好连接池的时候准备的连接数量
最大连接数(maxPoolSize):连接池中最多可以放多少个连接
最大等待时间(checkoutTimeout):连接池中没有连接时最长等待时间
最大空闲回收时间(maxIdleTime):连接池中的空闲连接多久没有使用就会回收
4.编写工具类
实现类对象:ComboPooledDataSource
获取连接:利用ComboPooledDataSource调用getConnection方法
/*
C3p0工具类
*/
public class C3P0Utils {
/*
实现类对象:ComboPooledDataSource
获取连接:利用ComboPooledDataSource调用getConnection方法
创建连接池对象
*/
private static ComboPooledDataSource cpds;
static{
cpds = new ComboPooledDataSource();
}
//获取连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = cpds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//关闭资源
/*
由于Connection从连接池中获取的
所以此处的Connection调用的close不是关闭资源,而是自动将Connection释放归还给连接池
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/*
测试类
*/
public class Demo01JDBC_C3P0 {
public static void main(String[] args) throws Exception {
//获取连接
Connection conn = C3P0Utils.getConnection();
//准备sql
String sql = "select * from user";
//获取PreparedStatement对象
PreparedStatement pst = conn.prepareStatement(sql);
//由于没有占位符,我们查询的是所有,所以不用为占位符赋值了
//执行sql
ResultSet resultSet = pst.executeQuery();
//处理结果集
while(resultSet.next()){
Object id = resultSet.getObject("id");
Object username = resultSet.getObject("username");
Object password = resultSet.getObject("password");
System.out.println(id+"..."+username+"..."+password);
}
//释放资源
C3P0Utils.close(conn,pst,resultSet);
}
}
2.连接池之Druid
1.概述:Druid连接池阿里巴巴开发的
2.好处:性能好,抗造
3.使用:
a.到jar包druid-1.1.6.jar
b.在src下面编写properties配置文件->druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day19?characterEncoding=utf8
username=root
password=root
c.编写工具类->
DruidDataSourceFactory.createDataSource(prop);->properties集合
public class DruidUtils {
private static DataSource dataSource;
static{
try {
//创建Properties集合
Properties properties = new Properties();
//利用字节输入流读取配置文件
InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//关闭资源
/*
由于Connection从连接池中获取的
所以此处的Connection调用的close不是关闭资源,而是自动将Connection释放归还给连接池
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public class Demo02JDBC_Druid {
public static void main(String[] args) throws Exception {
//获取连接
Connection conn = DruidUtils.getConnection();
//准备sql
String sql = "select * from user";
//获取PreparedStatement对象
PreparedStatement pst = conn.prepareStatement(sql);
//由于没有占位符,我们查询的是所有,所以不用为占位符赋值了
//执行sql
ResultSet resultSet = pst.executeQuery();
//处理结果集
while(resultSet.next()){
Object id = resultSet.getObject("id");
Object username = resultSet.getObject("username");
Object password = resultSet.getObject("password");
System.out.println(id+"..."+username+"..."+password);
}
//释放资源
DruidUtils.close(conn,pst,resultSet);
}
}
第三章.DBUtils工具类
1.准备工作

2.DBUtils的介绍
1.为什么要学:使用原生jdbc开发,代码很多,比较难,会影响我们的开发效率,而DBUtils可以大大的简化原生jdbc的开发
2.什么是DBUtils:是简化jdbc的一个工具包
3.使用:
a.导jar包->commons-dbutils-1.4.jar
4.三大核心对象
a.QueryRunner:定义了执行sql的方法
b.ResultSetHandler:有很多处理结果集的类
c.Dbutils:是一个类,里面定义了很多关闭资源以及操作事务的方法
3.QueryRunner
3.1.空参的QueryRunner的介绍以及使用
QueryRunner():
作用:需要我们维护连接,支持sql中使用占位符?
方法:
int update(Connection conn, String sql, Object... params)->针对于增删改
conn:连接对象
sql:sql语句
params:给sql中的?赋的值
比如:update(conn,"inert into category (cid,cname) values (?,?)",1,"蔬菜")
query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)->针对于
查询
conn:连接对象
sql:sql语句
rsh:处理结果集的方式
params:给sql中的?赋的值
准备数据:
create database 372_day05;
use 372_day05;
create table category(
cid int primary key,
cname varchar(100)
);
insert into category (cid,cname) values (1,'蔬菜'),(2,'服装'),(3,'水果'),(4,'箱包');
//添加功能
@Test
public void insert() throws SQLException {
//1.创建一个空参的QueryRunner对象
QueryRunner qr = new QueryRunner();
//2.获取连接
Connection connection = DruidUtils.getConnection();
//3.执行sql
qr.update(connection, "insert into category (cid,cname) values (?,?)", 5, "化妆品");
//4.关闭资源
DruidUtils.close(connection,null,null);
}
3.2.有参QueryRunner的介绍以及使用
QueryRunner(DataSource ds):
作用:自动维护连接对象,QueryRunner会自动从连接池中获取连接,用完自动归还
方法:
int update(String sql, Object... params)->针对于增删改
sql:要执行的sql语句
params:为?赋的值
query(String sql, ResultSetHandler<T> rsh, Object... params)->针对于查询
sql:要执行的查询sql语句
rsh:以什么方式处理结果集
params:给?赋的值
public class DruidUtils {
//创建对象
private static DataSource dataSource;
static {
try {
//读取配置文件
InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
//创建Properties集合
Properties properties = new Properties();
properties.load(is);
//获取连接池对象
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取DataSource的方法
public static DataSource getDs(){
return dataSource;
}
//获取连接
public static Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection();
return connection;
}
//关闭资源
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//添加功能
@Test
public void insert()throws Exception {
//1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
//2.由于连接对象不用我们维护,所以不用手动获取了,直接执行sql即可
qr.update("insert into category (cid,cname) values (?,?)",6,"手机");
//3.由于有参的QueryRunner工具类自动维护连接对象,所以我们也不用自己手动关闭
}
4.ResultSetHandler结果集
4.1.标准的JavaBean
什么是JavaBean:实体类
开发的过程中:JavaBean中的成员变量要和具体表中的字段要对应
一个标准的JavaBean如何编写:成员变量 构造 get/set 方法 hashcode和equals方法 toString
此类还需要实现Serializable

public class Category implements Serializable {
private Integer cid;
private String cname;
public Category() {
}
public Category(Integer cid, String cname) {
this.cid = cid;
this.cname = cname;
}
public int getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Category{" +
"cid=" + cid +
", cname='" + cname + '\'' +
'}';
}
}
4.2.BeanHandler
1.作用:将查询出来的结果集中的第一行数据封装成一个JavaBean对象
2. <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) ->针对查询
3.构造:BeanHandler(Class<T> type):
传递的class对象其实就是我们想要封装的JavaBean类的class对象
想将查询出来的结果封装成哪个JavaBean对象,就写哪个JavaBean的class对象
返回值
4.理解方法:
将查询出来的数据为JavaBean中的成员变量赋值
public class Demo01BeanHandler {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
/*
1.作用:将查询出来的结果集中的第一行数据封装成一个JavaBean对象
2. <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) ->针对查询
3.构造:BeanHandler(Class<T> type):
传递的class对象其实就是我们想要封装的JavaBean类的class对象
想将查询出来的结果封装成哪个JavaBean对象,就写哪个JavaBean的class对象
*/
//2.执行sql->如果new BeanHandler时不加泛型,那么query方法返回的是Object类型,当然可以强转
//Object query = qr.query("select * from category", new BeanHandler(Category.class));
/*
如果如果new BeanHandler时加泛型,那么query方法返回的是泛型的类型
*/
Category category = qr.query("select * from category", new BeanHandler<Category>(Category.class));
System.out.println(category);
}
}
4.3.BeanListHandler
1.作用:将查询出来的结果每一条数据都封装成一个一个的JavaBean对象,将这些JavaBean对象放在List集合中
2.构造:
BeanListHandler(Class<T> type)
传递的class对象其实就是我们想要封装的JavaBean类的class对象
3.理解方法:
将查询出来的数据为JavaBean中的成员变量赋值
public class Demo02BeanListHandler {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
/*
1.作用:将查询出来的结果每一条数据都封装成一个一个的JavaBean对象,将这些JavaBean对象放在List集合中
2.构造:
BeanListHandler(Class<T> type)
传递的class对象其实就是我们想要封装的JavaBean类的class对象
*/
//如果new BeanListHandler时不指定泛型,那么query返回的就是Object
//Object query = qr.query("select * from category", new BeanListHandler(Category.class));
//如果new BeanListHandler时指定泛型,那么query返回的就是放有指定泛型对象的List集合
List<Category> list = qr.query("select * from category", new BeanListHandler<Category>(Category.class));
//遍历list集合
for (Category category : list) {
System.out.println(category);
}
}
}
4.4.ScalarHandler
1.作用:处理单值查询结果,执行的select语句后,结果集只有1个->聚合函数
2.构造:
ScalarHandler(int columnIndex)->不常用->指定第几列
ScalarHandler(String columnName)->不常用->指定列名
ScalarHandler()
public class Demo03ScalarHandler {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
/*
1.作用:处理单值查询结果,执行的select语句后,结果集只有1个->聚合函数
2.构造:
ScalarHandler(int columnIndex)->不常用->指定第几列的第一个数据
ScalarHandler(String columnName)->不常用->指定列名的第一个数据
ScalarHandler()
*/
//Object o = qr.query("select * from category", new ScalarHandler(2));
//Object o = qr.query("select * from category", new ScalarHandler("cname"));
Object o = qr.query("select count(*) from category", new ScalarHandler());
System.out.println(o);
}
}
4.5.ColumnListHandler
1.作用: 将查询数据表结果集中的某一列数据,存储到List集合
2.构造:
ColumnListHandler():显示查询结果集中的第一列
ColumnListHandler(int columnIndex):指定显示查询结果集的第几列
ColumnListHandler(String columnIndex):指定显示查询结果集的列名
public class Demo04ColumnListHandler {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
/*
1.作用: 将查询数据表结果集中的某一列数据,存储到List集合
2.构造:
ColumnListHandler():显示查询结果集中的第一列
ColumnListHandler(int columnIndex):指定显示查询结果集的第几列
ColumnListHandler(String columnIndex):指定显示查询结果集的列名
*/
//空参的ColumnListHandler默认会将第一列数据放到list集合中
//List<Object> list = qr.query("select * from category", new ColumnListHandler());
//将指定第几列的数据放到List集合中
//List<Object> list = qr.query("select * from category", new ColumnListHandler(2));
//将指定列名中的数据放到List集合中
List<Object> list = qr.query("select * from category", new ColumnListHandler("cname"));
for (Object o : list) {
System.out.println(o);
}
}
}
第四章.事务
1.事务
1.1.事务_转账分析图

create table account(
id int primary key,
`name` varchar(100),
money double
);
1.2.实现转账(不加事务)
public class Demo01Transfer {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//2.获取连接
Connection connection = DruidUtils.getConnection();
//3.执行sql
qr.update(connection,"update account set money = money - 1000 where name = ?","taoge");
//System.out.println(1/0);
qr.update(connection,"update account set money = money + 1000 where name = ?","shiqing");
//4.关闭资源
DruidUtils.close(connection,null,null);
}
}
1.3.事务的介绍
1.作用:用于管理一组操作的,使这一组操作要不全部成功,要不全部失败
2.注意:mysql自带事务管理,但是它只能管理一条sql,如果想让mysql管理一组操作,需要手动操作事务,需要关闭mysql自带事务,开启手动事务
3.方法:Connection中的方法
a.setAutoCommit(false):关闭自动事务,开启手动事务
b.commit():提交事务,一旦提交,数据将永久保存,撤回不了
c.rollback():回滚事务,一旦回滚,数据还原
4.注意:以上三个方法,需要Connection对象去调用,而且这一组操作需要同一条连接对象
5.怎么使用:
try{
开启事务
报错了,走catch,一走catch证明有问题了,回滚事务,数据还原
提交事务
}catch(异常对象){
回滚事务
}
总结:
1.事务可以管理多条sql,使其要不全成功,要不全失败
2.mysql是再带事务管理的,但是一个事务只能管理一条sql
所以,我们如果想要让事务管理一组操作,需要将mysql自带的事务管理关闭,开启手动事务
1.4.DBUtils实现转账(添加事务)
public class Demo02Transfer {
public static void main(String[] args){
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//2.获取连接
Connection connection = DruidUtils.getConnection();
/*
a.setAutoCommit(false):关闭自动事务,开启手动事务
b.commit():提交事务,一旦提交,数据将永久保存,撤回不了
c.rollback():回滚事务,一旦回滚,数据还原
*/
try{
//开启手动事务,关闭mysql自动事务
connection.setAutoCommit(false);
//3.执行sql
qr.update(connection,"update account set money = money - 1000 where name = ?","taoge");
System.out.println(1/0);
qr.update(connection,"update account set money = money + 1000 where name = ?","shiqing");
//提交事务,让数据永久保存,无法还原
connection.commit();
System.out.println("转账成功!");
}catch(Exception e){
//事务回滚
try {
connection.rollback();
System.out.println("转账失败");
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
//4.关闭资源
DruidUtils.close(connection,null,null);
}
}
}
1.5.mysql中操作事务
#开启事务
BEGIN;
UPDATE account SET money = money - 1000 WHERE `name` = 'taoge';
UPDATE account SET money = money + 1000 WHERE `name` = 'shiqing';
#提交事务
COMMIT;
#回滚事务
ROLLBACK;
1.6.分层思想介绍以及架构搭建

分层:web层(和页面打交道) service层(和业务打交道) dao层(和数据库打交道)
分包:
取包名:一般都是公司域名倒着写,小写
cn.atguigu.domain -> 专门装实体类->JavaBean
cn.atguigu.web -> 专门装和页面有关系的类->web层相关类
cn.atguigu.dao -> 专门装和数据库有关的类->dao层相关类
cn.atguigu.service -> 专门装和业务层有关的类->service层相关类
cn.atguigu.utils ->专门装工具类
好处:可维护性强,可扩展性强,解耦,好看
1.6.1.转账_web层实现
public class AccountWeb {
public static void main(String[] args) {
//创建Scanner对象
Scanner sc = new Scanner(System.in);
System.out.println("请你输入要减钱的名字:");
String outName = sc.nextLine();
System.out.println("请你输入要加钱的名字:");
String inName = sc.nextLine();
System.out.println("请你输入要转的金额:");
int money = sc.nextInt();
//创建Service对象
AccountService accountService = new AccountService();
//调用service层的转账方法
try {
accountService.transfer(outName,inName,money);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1.6.2.转账_service层实现
public class AccountService {
/**
*
* @param outName 减钱的人
* @param inName 加钱的人
* @param money 转账的金额
*/
public void transfer(String outName, String inName, int money) throws SQLException {
//创建dao对象
AccountDao accountDao = new AccountDao();
//调用dao层方法
accountDao.outMoney(outName,money);
//System.out.println(1/0);
accountDao.inMoney(inName,money);
}
}
1.6.3.转账_dao层实现
public class AccountDao {
//减钱
public void outMoney(String outName, int money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//获取连接
Connection connection = DruidUtils.getConnection();
//准备sql
String sql = "update account set money = money-? where name = ?";
//执行sql
qr.update(connection,sql,money,outName);
//关闭资源
DruidUtils.close(connection,null,null);
}
//加钱
public void inMoney(String inName, int money)throws SQLException{
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//获取连接
Connection connection = DruidUtils.getConnection();
//准备sql
String sql = "update account set money = money+? where name = ?";
//执行sql
qr.update(connection,sql,money,inName);
//关闭资源
DruidUtils.close(connection,null,null);
}
}

1.6.4.在service层添加事务(传递连接)
public class AccountService {
/**
*
* @param outName 减钱的人
* @param inName 加钱的人
* @param money 转账的金额
*/
public void transfer(String outName, String inName, int money) throws SQLException {
//获取连接
Connection connection = DruidUtils.getConnection();
try{
//开启事务
connection.setAutoCommit(false);
//创建dao对象
AccountDao accountDao = new AccountDao();
//调用dao层方法
accountDao.outMoney(connection,outName,money);
System.out.println(1/0);
accountDao.inMoney(connection,inName,money);
//提交事务
connection.commit();
System.out.println("转账成功");
}catch(Exception e){
//回滚事务
connection.rollback();
System.out.println("转账失败");
}finally {
DruidUtils.close(connection,null,null);
}
}
}
1.6.5.修改dao层代码
public class AccountDao {
//减钱
public void outMoney(Connection connection,String outName, int money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//准备sql
String sql = "update account set money = money-? where name = ?";
//执行sql
qr.update(connection,sql,money,outName);
//关闭资源
//DruidUtils.close(connection,null,null);
}
//加钱
public void inMoney(Connection connection,String inName, int money)throws SQLException{
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//准备sql
String sql = "update account set money = money+? where name = ?";
//执行sql
qr.update(connection,sql,money,inName);
//关闭资源
//DruidUtils.close(connection,null,null);
}
}
问题:
直接从连接池中获取连接的动作不应该是service直接干的,但是如果service不干从连接池中获取连接这个事儿,service中的事务操作失效了,dao层也不能接收到Connection对象了
解决思想:
1.不在service层中直接从连接池中获取连接
2.还要保证service和dao都能使用到连接对象,还得是同一条(能保证事务生效)
2.事务的特性以及隔离级别
2.1.事务特性:ACID
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)事务前后数据的完整性必须保持一致。
- 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是效率会非常低。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
2.2 并发访问问题
如果不考虑隔离性,事务存在3中并发访问问题。
- 脏读:一个事务读到了另一个事务未提交的数据.
- 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
- 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
2.3 隔离级别:解决问题
- 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
- read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a)存在:3个问题(脏读、不可重复读、虚读)。
b)解决:0个问题 - read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a)存在:2个问题(不可重复读、虚读)。
b)解决:1个问题(脏读) - repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a)存在:1个问题(虚读)。
b)解决:2个问题(脏读、不可重复读)
4.serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a)存在:0个问题。
b)解决:3个问题(脏读、不可重复读、虚读)
- 安全和性能对比
- 安全性:
serializable > repeatable read > read committed > read uncommitted - 性能 :
serializable < repeatable read < read committed < read uncommitted
- 安全性:
- 常见数据库的默认隔离级别:
- MySql:
repeatable read - Oracle:
read committed
- MySql:
2.4 演示
- 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
- 查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;

- 设置数据库的隔离级别
set session transactionisolation level级别字符串- 级别字符串:
readuncommitted、read committed、repeatable read、serializable - 例如:
set session transaction isolation level read uncommitted;
- 读未提交:readuncommitted
- A窗口设置隔离级别
- AB同时开始事务
- A 查询
- B 更新,但不提交
- A 再查询?— 查询到了未提交的数据
- B 回滚
- A 再查询?— 查询到事务开始前数据
- A窗口设置隔离级别
- 读已提交:read committed
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新、但不提交
- A再查询?—数据不变,解决问题【脏读】
- B提交
- A再查询?—数据改变,存在问题【不可重复读】
- A窗口设置隔离级别
- 可重复读:repeatable read
- A窗口设置隔离级别
- AB 同时开启事务
- A查询
- B更新, 但不提交
- A再查询?—数据不变,解决问题【脏读】
- B提交
- A再查询?—数据不变,解决问题【不可重复读】
- A提交或回滚
- A再查询?—数据改变,另一个事务
- A窗口设置隔离级别
- 串行化:serializable
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新?—等待(如果A没有进一步操作,B将等待超时)
- A回滚
- B 窗口?—等待结束,可以进行操作
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)事务前后数据的完整性必须保持一致。
- 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是隔离级别越高,效率会非常低。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
如果不考虑隔离性,事务存在3中并发访问问题。(如果隔离级别低,事务跟事务之间有可能互相影响)
1. 脏读:一个事务读到了另一个事务未提交的数据.
2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
总结:
我们最理想的状态是:一个事务和其他事务互不影响
但是如果不考虑隔离级别的话,就会出现多个事务之间互相影响
而事务互相影响的表现方式为:
脏读
不可重复读
虚读/幻读
