事务
Transaction :
其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚)。
为什么要有事务?
为了确保逻辑的成功。 例子: 银行的转账。
使用命令行方式演示事务
* 开启事务
start transaction;
* 提交或者回滚事务
commit; 提交事务, 数据将会写到磁盘上的数据库
rollback ; 数据回滚,回到最初的状态。
1. 关闭自动提交功能。
2. 演示事务
使用代码方式演示事务
代码里面的事务,主要是针对连接来的
1. 通过conn.setAutoCommit(false )来关闭自动提交的设置。
2. 提交事务 conn.commit();
3. 回滚事务 conn.rollback();
实例:TransactionDemo01/src/it/cast/test/demo/TestDemo01.java
public class TestDemo01 {
@Test
public void testTransaction() {
Connection conn =null;
PreparedStatement ps =null;
try {
conn = JDBCUtil.jdbcConn();
//jdbc默认的实物事物是自动提交的,关闭自动提交的功能
conn.setAutoCommit(false);
String sql="update account set mony = mony-? where id= ?";
ps = conn.prepareStatement(sql);
//给id为1的账户增加100元
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
int a=10/0;//设置的错误
//给id为2的账户减少100元
ps.setInt(1, -100);
ps.setInt(2, 2);
ps.executeUpdate();
//提交事务
conn.commit();
} catch (SQLException e) {
try {
//如果发生错误,数据回滚
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtil.resease( ps, conn);
}
}
}
事务的特性
* 原子性
> 指的是 事务中包含的逻辑,不可分割。
* 一致性
> 指的是 事务执行前后。数据完整性保持一致
* 隔离性
> 指的是 事务在执行期间不应该受到其他事务的影响
* 持久性
> 指的是 事务执行成功,那么数据应该持久保存到磁盘上。
事务的安全隐患
不考虑隔离级别设置,那么会出现以下问题。
* 读
脏读:一个事务读到了另外一个未提交的并行事务写的数据。
不可重复读 :一个事务读到了另外一个事务提交的数据 ,造成了前后两次查询结果不一致。
幻读:一个事务读到了另一个事务insert的数据 ,造成前后查询结果不一致 。
* 写
丢失更新
隔离级别
Read Uncommitted:读未提交
引发问题: 脏读
Read Committed:读已提交【oracle默认】
解决: 脏读 , 引发: 不可重复读
Repetable:重复读【mysql默认】
解决: 脏读 、 不可重复读 , 未解决: 幻读
该事务级别不受其他事务的影响,在本次连接状态下,即使其他的事务连接修改了数据,本次读到的数据也不会改变, 和上次查询结果一致。
Seializable:可串行化
解决: 脏读、 不可重复读 、 幻读。
mySql 默认的隔离级别是 可重复读
Oracle 默认的隔离级别是 读已提交
读未提交 演示:
1. 设置A窗口的隔离级别为 读未提交
2. 两个窗口都分别开启事务
读已提交演示
1. 设置A窗口的隔离级别为 读已提交
2. A B 两个窗口都开启事务, 在B窗口执行更新操作。
3. 在A窗口执行的查询结果不一致。 一次是在B窗口提交事务之前,一次是在B窗口提交事务之后。
这个隔离级别能够屏蔽 脏读的现象, 但是引发了另一个问题 ,不可重复读。
可串行化
如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利, 谁后打开事务,谁就只能得着,等前面的那个事务,提交或者回滚后,才能执行。 但是这种隔离级别一般比较少用。 容易造成性能上的问题。 效率比较低。按
* 按效率划分,从高到低
> 读未提交 > 读已提交 > 可重复读 > 可串行化
* 按拦截程度 ,从高到底
> 可串行化 > 可重复读 > 读已提交 > 读未提交
需要掌握的
1. 在代码里面会使用事务
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
2. 事务只是针对连接连接对象,如果再开一个连接对象,那么那是默认的提交。
3. 事务是会自动提交的。
丢失更新
解决丢失更新
* 悲观锁
> 可以在查询的时候,加入 for update
* 乐观锁
> 要求程序员自己控制。 (需要额外加一个字段)
数据库连接池
1. 数据库的连接对象创建工作,比较消耗性能。
2.一开始现在内存中开辟一块空间(集合) , 一开先往池子里面放置 多个连接对象。 后面需要连接的话,直接从池子里面去。不要去自己创建连接了。 使用完毕, 要记得归还连接。确保连接对象能循环利用。
自定义数据库连接池
代码实现:ConnectionPool01/src/it/cast/connectionpool/demo1/MyDataSource.java
/**
* 这是一个数据库连接池,一开始池子里面有10个连接:
* 1.开始创建10个连接
* 2.用getConnection获取连接
* 3.用addBack的方法归还连接
* 4.扩容
*/
public class MyDataSource implements DataSource{
List<Connection>list=new ArrayList<Connection>();
//一开始在构造方法创建10个连接
public MyDataSource() {
super();
for (int i = 0; i < 5; i++) {
Connection conn = JDBCUtil.jdbcConn();
list.add(conn);
}
}
//连接池对外公布获取连接的方法
public Connection getConnection() throws SQLException {
//扩容方法
//连接的时候,看池子里面还有连接吗
if(list.size()==0) {
for (int i = 0; i < 10; i++) {
Connection conn = JDBCUtil.jdbcConn();
list.add(conn);
}
}
Connection conn=list.remove(0);
return conn;
}
//连接池对外提供归还连接的方法
public void addBack(Connection conn) {
list.add(conn);
}
简单的测试连接池:ConnectionPool01/src/it/cast/connectionpool/demo1/TestPool.java
public class TestPool {
@Test
public void tsetPool() {
MyDataSource myDataSource=new MyDataSource();
Connection conn=null;
PreparedStatement rs = null;
try {
conn=myDataSource.getConnection();
String sql="insert into account values(null,'xilali',10)";
rs = conn.prepareStatement(sql);
rs.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//归还连接对象
myDataSource.addBack(conn);
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// JDBCUtil.resease(st, conn);
}
}
}
出现的问题:
1. 需要额外记住 addBack方法
2. 单例。
3. 无法面向接口编程。
UserDao dao = new UserDaoImpl();
dao.insert();
DataSource dataSource = new MyDataSource();
因为接口里面没有定义addBack方法。
4. 怎么解决? 以addBack 为切入点。
解决自定义数据库连接池出现的问题。
> 由于多了一个addBack 方法,所以使用这个连接池的地方,需要额外记住这个方法,并且还不能面向接口编程。
> 我们打算修改接口中的那个close方法。 原来的Connection对象的close方法,是真的关闭连接。
> 打算修改这个close方法,以后在调用close, 并不是真的关闭,而是归还连接对象。
如何扩展某一个方法?
> 原有的方法逻辑,不是我们想要的。 想修改自己的逻辑
1. 直接改源码 无法实现。
2. 继承, 必须得知道这个接口的具体实现是谁。
3. 使用装饰者模式
装饰着模式举例:WraperDemo
接口:Witer
public interface Waiter {
void service();
}
实现该接口的子类:Witress
public class Witress implements Waiter {
//女服务员
public void service() {
System.out.println("我来为你服务");
}
}
装饰类:WaitressWraper
public class WaitressWraper implements Waiter{
Waiter waiter;
public WaitressWraper(Waiter waiter) {
super();
this.waiter = waiter;
}
@Override
public void service() {
System.out.println("开心");
waiter.service();
}
}
测试方法:TestWraper
public class TestWraper {
@Test
public void testWraper() {
//传入的是女服务员
WaitressWraper waitressWraper=new WaitressWraper(new Witress());
waitressWraper.service();
}
}
使用装饰着模式改善自定义数据库:ConnectionPool2
连接池:MyDataSource
public class MyDataSource implements DataSource{
List<Connection>list=new ArrayList<Connection>();
//一开始在构造方法创建10个连接
public MyDataSource() {
super();
for (int i = 0; i < 10; i++) {
Connection conn = JDBCUtil.jdbcConn();
list.add(conn);
}
}
//连接池对外公布获取连接的方法
public Connection getConnection() throws SQLException {
//扩容方法
//连接的时候,看池子里面还有连接吗
if(list.size()==0) {
for (int i = 0; i < 5; i++) {
Connection conn = JDBCUtil.jdbcConn();
list.add(conn);
}
}
//去除list集合中的第一个链接对象
Connection conn=list.remove(0);
//在把值传出去之前需要包装一下
Connection connection = new WraperMaDataSource(conn, list);
return connection;
}
//连接池对外提供归还连接的方法
public void addBack(Connection conn) {
list.add(conn);
}
连接池的装饰类:WraperMaDataSource
public class WraperMaDataSource implements Connection{
Connection conn=null;
List<Connection>list=new ArrayList<>();
public WraperMaDataSource(Connection conn, List<Connection> list) {
super();
this.conn = conn;
this.list = list;
}
//装饰类的close方法,归还连接对象
@Override
public void close() throws SQLException {
System.out.println("使用close前list集合中的连接对象个数"+list.size());
list.add(conn);
System.out.println("使用close后list集合中的连接对象个数"+list.size());
}
//因为之后需要用到这个方法,所以复写它
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}
简单的测试连接池:TestPool
public class TestPool {
@Test
public void tsetPool() {
MyDataSource myDataSource=new MyDataSource();
Connection conn=null;
PreparedStatement ps = null;
try {
conn=myDataSource.getConnection();
String sql="insert into account values(null,'shei',10)";
ps = conn.prepareStatement(sql);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
}
开源连接池
DBCP
1. 导入jar文件
commons-dbcp-1.4.jar
commons-pool-1.5.6.jar
2. 不使用配置文件
public class DBCPDemo01 {
@Test
public void testDBCP01() {
Connection conn = null;
PreparedStatement ps = null;
try {
BasicDataSource dataSource=new BasicDataSource();
//代码配置
dataSource.setUrl("jdbc:mysql://localhost/bank");
dataSource.setUsername("root");
dataSource.setPassword("19930903");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
conn = dataSource.getConnection();
String sql="insert into account values(null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "xiao");
ps.setInt(2, 65);
ps.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
}
使用配置文件连接:dbcpconfig.properties
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bank
username=root
password=
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
public class DBCPDemo02 {
@Test
public void testDBCP02() {
Connection conn = null;
PreparedStatement ps = null;
try {
BasicDataSourceFactory factory=new BasicDataSourceFactory();
Properties properties=new Properties();
InputStream is=new FileInputStream("src//dbcpconfig.properties");
properties.load(is);
DataSource dataSource = factory.createDataSource(properties);
conn = dataSource.getConnection();
String sql="insert into account values(null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "dao");
ps.setInt(2, 65);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
}
C3P0
使用代码的方式使用C3P0数据库池
public class C3P0Demo1 {
@Test
public void testC3P01() {
PreparedStatement ps = null;
Connection conn =null;
try {
//获取datasource
ComboPooledDataSource dataSource=new ComboPooledDataSource();
//设立连接参数
dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
dataSource.setUser("root");
dataSource.setPassword("19930903");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
//得到连接对象
conn = dataSource.getConnection();
String sql="insert into account values(null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "haha");
ps.setInt(2, 65);
ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
}
使用XML配置文件的形式连接数据库
配置文件:c3p0-config.xml 【名字必须是这个】
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost/bank</property>
<property name="user">root</property>
<property name="password">19930903</property>
<property name="driverClass">com.mysql.jdbc.Driver</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>
</default-config>
</c3p0-config>
public class C3P0Demo2 {
@Test
public void testC3P02() {
PreparedStatement ps = null;
Connection conn =null;
try {
//获取datasource
ComboPooledDataSource dataSource=new ComboPooledDataSource();
//得到连接对象
conn = dataSource.getConnection();
String sql="insert into account values(null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "haha");
ps.setInt(2, 65);
ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
}
原先的JDBCUtil的改变:不需要最初的配置文件了
public class JDBCUtil {
static ComboPooledDataSource dataSource=null;
static {
dataSource=new ComboPooledDataSource();
}
/*
* jabc的连接
*/
public static Connection jdbcConn() throws SQLException {
return dataSource.getConnection();
}
/*
* 关闭数据库的方法,释放资源
*/
public static void resease(ResultSet rs,Statement st,Connection conn) {
rsclose(rs);
stclose(st);
connclose(conn);
}
public static void resease(Statement st,Connection conn) {
stclose(st);
connclose(conn);
}
private static void rsclose(ResultSet rs) {
try {
if(rs!=null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void stclose(Statement st) {
try {
if(st!=null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void connclose(Connection conn) {
try {
if(conn!=null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
DBUtils
增删改:
//dbutils 只是帮我们简化了CRUD 的代码, 但是连接的创建以及获取工作。 不在他的考虑范围
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//增加
queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000);
//删除
queryRunner.update("delete from account where id = ?", 5);
//更新
queryRunner.update("update account set money = ? where id = ?", 10000000 , 6);
查询:
1. 直接new接口的匿名实现类
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
Account account = queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>(){
@Override
public Account handle(ResultSet rs) throws SQLException {
Account account = new Account();
while(rs.next()){
String name = rs.getString("name");
int money = rs.getInt("money");
account.setName(name);
account.setMoney(money);
}
return account;
}
}, 6);
System.out.println(account.toString());
2. 直接使用框架已经写好的实现类。
* 查询单个对象
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//查询单个对象
Account account = queryRunner.query("select * from account where id = ?",
new BeanHandler<Account>(Account.class), 8);
* 查询多个对象
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
List<Account> list = queryRunner.query("select * from account ",
new BeanListHandler<Account>(Account.class));
完整实例:/ConnectionC3P0/src/it/cast/DBUtils/DBUtilsDemo.java
public class DBUtilsDemo {
@Test
public void testDBUtils01() throws SQLException {
//使用c3p0获取datasource
ComboPooledDataSource dataSource=new ComboPooledDataSource();
QueryRunner queryRunner=new QueryRunner(dataSource);
//增加
// queryRunner.update("insert into account values(null,?,?)", "sanlu",1000);
//删除
// queryRunner.update("delete from account where(id=?)", 11);
//更新
// queryRunner.update("update account set mony = ? where id =?", 10000,5);
//查询
//查询到的数据还是在Result里边,然后调用handle()方法,由用户手动封装。
/*Account account = queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>() {
@Override
public Account handle(ResultSet rs) throws SQLException {
Account account=new Account();
while(rs.next()) {
String name = rs.getString("name");
int mony = rs.getInt("mony");
account.setMony(mony);
account.setName(name);
}
return account;
}
}, 1);
System.out.println(account.toString());
*/
//查单个对象
/*Account account = queryRunner.query("select * from account where id = ?",
new BeanHandler<Account>(Account.class), 6);
System.out.println(account.toString());
*/
//查询多个对象
List<Account> list = queryRunner.query("select * from account", new BeanListHandler<Account>(Account.class));
for (Account account : list) {
System.out.println(account.toString());
}
}
}
装查询结果数据的对象:Account
public class Account {
private String name=null;
private int mony=0;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMony() {
return mony;
}
public void setMony(int mony) {
this.mony = mony;
}
@Override
public String toString() {
return "Account [name=" + name + ", mony=" + mony + "]";
}
}
ResultSetHandler 常用的实现类
以下两个是使用频率最高的
BeanHandler, 查询到的单个数据封装成一个对象
BeanListHandler, 查询到的多个数据封装 成一个List<对象>
ArrayHandler, 查询到的单个数据封装成一个数组
ArrayListHandler, 查询到的多个数据封装成一个集合 ,集合里面的元素是数组。
MapHandler, 查询到的单个数据封装成一个map
MapListHandler,查询到的多个数据封装成一个集合 ,集合里面的元素是map。
ColumnListHandler
KeyedHandler
ScalarHandler
自定义DButils
元参数:Meata data
描述数据的数据 String sql , 描述这份sql字符串的数据叫做元数据
数据库元数据 : DatabaseMetaData
参数元数据 :ParameterMetaData(用的比较多)
结果集元数据 :ResultSetMetaData
bean类:ConnectionC3P0/src/it/cast/domain/Account.java
/**
* 装查询结果数据的对象
*/
public class Account {
private String name=null;
private int mony=0;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMony() {
return mony;
}
public void setMony(int mony) {
this.mony = mony;
}
@Override
public String toString() {
return "Account [name=" + name + ", mony=" + mony + "]";
}
}
定义查询结果数据封装的规则:ResultSetHandler
public interface ResultSetHandler <T> {
//自定义处理数据的handle函数
T handle(ResultSet rs);
}
增删改查:ConnectionC3P0/src/commoncrud/CommonCRUDUtil.java
public class CommonCRUDUtil {
@Test
public void testQuery() {
query("select * from account where id = ?", new ResultSetHandler<Account>() {
@Override
public Account handle(ResultSet rs) {
Account account=new Account();
try {
while (rs.next()) {
String name = rs.getString("name");
int mony = rs.getInt("mony");
account.setMony(mony);
account.setName(name);
System.out.println(account.toString());
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}, 1);
}
/**
* @param sql :要处理的sql语句
* @param handler :自定义的用户处理查询结果集的对象
* @param args :可变参数,有几个传几个
* @return :返回查询结果
*/
public <T>T query(String sql,ResultSetHandler<T> handler, Object...args) {
Connection conn =null;
PreparedStatement ps =null;
ResultSet rs=null;
try {
conn = JDBCUtil.jdbcConn();
ps = conn.prepareStatement(sql);
//获取有几个占位符(?)
ParameterMetaData metaData = ps.getParameterMetaData();
int count = metaData.getParameterCount();
for (int i = 0; i < count; i++) {
ps.setObject(i+1, args[i]);
}
//执行查询
rs = ps.executeQuery();
//返回结果集,让用户去处理数据
T t=(T)handler.handle(rs);
return t;
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.resease(rs, ps, conn);
}
return null;
}
/**********************************************************/
@Test
public void testUpdate() {
//增
// update2("insert into account values(null,?,?)","zhangsan",10086,6,8,254);
//删
// update2("delete from account where id = ?", 9);
//改
update2("update account set mony = ? where id = ?", 99999,5);
}
/**
* 使用数组进行遍历
* @param sql sql需要操作的sql语句
* @param args 可变参数,有几个占位符就写几个参数
*/
public void update1(String sql,Object...args) {
Connection conn =null;
PreparedStatement ps =null;
try {
conn = JDBCUtil.jdbcConn();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1, args[i]);
}
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
/**
* 使用?的个数进行循环
* @param sql需要操作的sql语句
* @param args 可变参数,有几个占位符就写几个参数
*/
public void update2(String sql,Object...args) {
Connection conn =null;
PreparedStatement ps =null;
try {
conn = JDBCUtil.jdbcConn();
ps = conn.prepareStatement(sql);
//获取元参数对象
ParameterMetaData metaData = ps.getParameterMetaData();
int count = metaData.getParameterCount();
for (int i = 0; i < count; i++) {
ps.setObject(i+1, args[i]);
}
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.resease(ps, conn);
}
}
}