事务
事务四大特性(ACID)
事务:
生活中的事务: 指的是比较正式的事情
数据库中的事务: 指的是一组最小逻辑操作单元,这个操作它包含了很多步骤,组成这个事务的这些步骤,是整体不可再分割的,这些步骤要么同时成功,要么同时失败
转账: 张三给李四转账1000元
1.张三的账户上减去1000元
出现异常
2.李四的账户上增加1000元
上面减钱和加钱这两步操作 必须在同一事务中进行。
事务的四大特性(ACID):
1.原子性(Atomicity):
原子性是指事务是一个不可分割的工作单位
事务中的操作要么都发生,要么都不发生
2.一致性:
事务必须使数据库从一个一致性状态变换到另外一个一致性状态
3.隔离性:
事务的隔离性是多个用户并发访问数据库时
数据库为每一个用户开启的事务
不能被其他事务的操作数据所干扰
多个并发事务之间要相互隔离
4.持久性:
持久性是指一个事务一旦被提交
它对数据库中数据的改变就是永久性的
接下来即使数据库发生故障也不应该对其有任何影响
默认情况下,Connection对象处于自动提交模式下
这意味着它在执行每个语句后都会自动提交更改。
如果禁用了自动提交模式,那么要提交更改就必须显式调用commit方法
否则无法保存数据库更改。
//void setAutoCommit ( boolean autoCommit)
throws SQLException将此连接的自动提交模式设置为给定状态
如果连接处于自动提交模式下,则它的所有SQL语句将被执行并作为单个事务提交。
否则,它的SQL语句将聚集到事务中,
直到调用commit方法或rollback方法为止。
默认情况下,新连接处于自动提交模式。
事务隔离级别
★不考虑隔离性会出现的读问题
脏读:
在一个事务中读取到另一个事务没有提交的数据
不可重复读:
在一个事务中,两次查询的结果不一致(针对的update操作)
不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。
虚读(幻读):
在一个事务中,两次查询的结果不一致(针对的insert操作)
无法演示出来,MySQL已经默认避免了
★MySQL四种隔离级别
通过设置数据库的隔离级别来避免上面的问题
read uncommitted 读未提交 上面的三个问题都会出现
read committed 读已提交 可以避免脏读的发生Oracle默认级别
repeatable read 可重复读 可以避免脏读和不可重复读的发生MySQL默认级别
serializable 串行化 可以避免所有的问题
●演示脏读的发生:
将数据库的隔离级别设置成 读未提交
set session transaction isolation level read uncommitted;
查看数据库的隔离级别
select @@tx_isolation;
演示:
打开两个窗口进行演示: 给两个窗口设置好相同的隔离级别
开启事务: start transaction;
修改数据: update bank set money=1500 where username='lisi';
让另一个窗口开启事务 查询数据 他查到了 就是脏读
我这边窗口 一回滚(rollback),钱又没过去
●避免脏读的发生,将隔离级别设置成 读已提交
set session transaction isolation level read committed;
不可避免不可重复读的发生
●避免不可重复读的发生 经隔离级别设置成 可重复读
set session transaction isolation level repeatable read;
●演示串行化 可以避免所有的问题
set session transaction isolation level serializable;
我这边的事务不提交,那边的事务无法执行锁表的操作
四种隔离级别的效率
(read uncommitted)>(read committed)>(repeatable read)>(serializable)
四种隔离级别的安全性
(read uncommitted)<(read committed)<(repeatable read)<(serializable)
开发中绝对不允许脏读发生
mysql中默认级别:repeatable read
oracle中默认级别:read committed
java中控制隔离级别:
Connection的api
void setTransactionIsolation(int level)
level是常量
银行转账
public class JDBCBank {
public static void main ( String [ ] args) throws Exception {
Connection conn = JDBCUtils . getConnection ( ) ;
conn. setAutoCommit ( false ) ;
Savepoint one = null ;
try {
String sql1 = "update bank set money=money-1000 where username='zhangsan'" ;
PreparedStatement preparedStatement = conn. prepareStatement ( sql1) ;
preparedStatement. executeUpdate ( ) ;
String sql2 = "update bank set money=money+1000 where username='lisi'" ;
PreparedStatement preparedStatement2 = conn. prepareStatement ( sql2) ;
preparedStatement2. executeUpdate ( ) ;
one = conn. setSavepoint ( ) ;
System . out. println ( "第二次转账" ) ;
String sql3 = "update bank set money=money-1000 where username='zhangsan'" ;
PreparedStatement preparedStatement3 = conn. prepareStatement ( sql3) ;
preparedStatement3. executeUpdate ( ) ;
int j = 1 / 0 ;
String sql4 = "update bank set money=money+1000 where username='lisi'" ;
PreparedStatement preparedStatement4 = conn. prepareStatement ( sql4) ;
preparedStatement4. executeUpdate ( ) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
if ( one == null ) {
conn. rollback ( ) ;
} else {
System . out. println ( "执行了" ) ;
conn. rollback ( one) ;
}
} finally {
conn. commit ( ) ;
}
}
}
连接池
为什么要有连接池?
由于建立数据库连接是一种非常耗时、耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,使用完毕后再归还到连接池中。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等
1.DBCP连接池
DBCP ( DataBase Connection Pool ) 数据库连接池
是java数据库连接池的一种 由Apache 开发
通过数据库连接池,可以让程序自动管理数据库连接的释放和断开
1. 依赖DBCP连接池的两个jar包,还要有MySQL 的驱动jar包
2. 使用:
方式1 :硬编码 参数写死在代码
BasicDataSource ds = new BasicDataSource ( ) ;
ds. setDriverClassName ( "com.mysql.jdbc.Driver" ) ;
ds. setUrl ( "jdbc:mysql://localhost:3306/my_database" ) ;
ds. setUsername ( "root" ) ;
ds. setPassword ( "123456" ) ;
Connection conn = ds. getConnection ( ) ;
方式2 :采用配置文件方式
Properties p = new Properties ( ) ;
p. load ( new FileReader ( "dbcp.properties" ) ) ;
DataSource ds = BasicDataSourceFactory . createDataSource ( p) ;
Connection c = ds. getConnection ( ) ;
硬编码
public class DBCPTest1 {
public static void main ( String [ ] args) throws Exception {
BasicDataSource ds = new BasicDataSource ( ) ;
ds. setDriverClassName ( "com.mysql.jdbc.Driver" ) ;
ds. setUrl ( "jdbc:mysql://localhost:3306/my_database" ) ;
ds. setUsername ( "root" ) ;
ds. setPassword ( "123456" ) ;
Connection conn = ds. getConnection ( ) ;
String sql = "insert into bank values(?,?);" ;
PreparedStatement preparedStatement
= conn. prepareStatement ( sql) ;
preparedStatement. setString ( 1 , "tom" ) ;
preparedStatement. setInt ( 2 , 8000 ) ;
int i = preparedStatement. executeUpdate ( ) ;
System . out. println ( i) ;
preparedStatement. close ( ) ;
}
}
配置文件方式
public class DBCPTest2 {
public static void main ( String [ ] args) throws Exception {
Properties properties = new Properties ( ) ;
properties. load ( new FileReader ( "dbcp.properties" ) ) ;
DataSource ds = BasicDataSourceFactory . createDataSource ( properties) ;
Connection conn = ds. getConnection ( ) ;
String sql = "select * from bank;" ;
PreparedStatement preparedStatement = conn. prepareStatement ( sql) ;
ResultSet resultSet = preparedStatement. executeQuery ( ) ;
while ( resultSet. next ( ) ) {
String string = resultSet. getString ( "username" ) ;
System . out. println ( string) ;
}
preparedStatement. close ( ) ;
resultSet. close ( ) ;
}
}
DBCP配置文件
#连接基本设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/my_database
username=root
password=123456
#<!--扩展配置 了解-->
#初始化连接
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 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
2.C3P0连接池
C3P0是一个开源的JDBC连接池
它实现了数据源和JNDI绑定
支持JDBC3规范和JDBC2的标准扩展。
目前使用它的开源项目有Hibernate 、Spring 等
c3p0与dbcp区别
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接的功能
1. 依赖C3P0连接池的一个jar包,还要有MySQL 的驱动jar包
2. 使用:
方式1 :硬编码 参数写死在代码
ComboPooledDataSource ds
= new ComboPooledDataSource ( ) ;
ds. setDriverClass ( "com.mysql.jdbc.Driver" ) ;
ds. setJdbcUrl ( "jdbc:mysql:///my_database" ) ;
ds. setUser ( "root" ) ;
ds. setPassword ( "123456" ) ;
Connection conn = ds. getConnection ( ) ;
方式2 :采用配置文件方式
要求1 :
配置文件的名称:
c3p0. properties
或者 c3p0- config. xml
要求2 :
配置文件的路径:
src下
ComboPooledDataSource ds
= new ComboPooledDataSource ( ) ;
Connection conn = ds. getConnection ( ) ;
硬编码
public class C3P0Test1 {
public static void main ( String [ ] args) throws PropertyVetoException , SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource ( ) ;
ds. setDriverClass ( "com.mysql.jdbc.Driver" ) ;
ds. setJdbcUrl ( "jdbc:mysql:///my_database" ) ;
ds. setUser ( "root" ) ;
ds. setPassword ( "123456" ) ;
Connection conn = ds. getConnection ( ) ;
String sql = "insert into bank values(?,?);" ;
PreparedStatement preparedStatement
= conn. prepareStatement ( sql) ;
preparedStatement. setString ( 1 , "wang" ) ;
preparedStatement. setInt ( 2 , 2000 ) ;
int i = preparedStatement. executeUpdate ( ) ;
System . out. println ( i) ;
preparedStatement. close ( ) ;
}
}
配置文件方式
public class C3P0Test2 {
public static void main ( String [ ] args) throws Exception {
ComboPooledDataSource ds = new ComboPooledDataSource ( ) ;
Connection conn = ds. getConnection ( ) ;
String sql = "select * from bank;" ;
PreparedStatement preparedStatement = conn. prepareStatement ( sql) ;
ResultSet resultSet = preparedStatement. executeQuery ( ) ;
while ( resultSet. next ( ) ) {
String string = resultSet. getString ( "username" ) ;
System . out. println ( string) ;
}
preparedStatement. close ( ) ;
resultSet. close ( ) ;
}
}
C3P0配置文件
#连接基本设置
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql:///my_database
c3p0.user=root
c3p0.password=123456
3.Druid连接池
Druid 阿里德鲁伊连接池
Duridv 是阿里巴巴开源平台上一个数据库连接池实现
它结合了C3P0、DBCP、PROXOOL等DB池的优点
同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况
可以说是针对监控而生的DB连接池
1. 依赖Druid 连接池的一个jar包,还要有MySQL 的驱动jar包
2. 使用:
方式1 :硬编码 参数写死在代码
DruidDataSource ds
= new DruidDataSource ( ) ;
ds. setDriverClassName ( "com.mysql.jdbc.Driver" ) ;
ds. setUrl ( "jdbc:mysql:///my_database" ) ;
ds. setUsername ( "root" ) ;
ds. setPassword ( "123456" ) ;
Connection c = ds. getConnection ( ) ;
方式2 :采用配置文件方式
Properties p = new Properties ( ) ;
p. load ( new FileReader ( "druid.properties" ) ) ;
DataSource ds
= DruidDataSourceFactory . createDataSource ( p) ;
Connection c = ds. getConnection ( ) ;
硬编码
public class DruidTest1 {
public static void main ( String [ ] args) throws Exception {
DruidDataSource ds = new DruidDataSource ( ) ;
ds. setDriverClassName ( "com.mysql.jdbc.Driver" ) ;
ds. setUrl ( "jdbc:mysql://localhost:3306/my_database" ) ;
ds. setUsername ( "root" ) ;
ds. setPassword ( "123456" ) ;
Connection conn = ds. getConnection ( ) ;
String sql = "insert into bank values(?,?);" ;
PreparedStatement preparedStatement
= conn. prepareStatement ( sql) ;
preparedStatement. setString ( 1 , "jack" ) ;
preparedStatement. setInt ( 2 , 6666 ) ;
int i = preparedStatement. executeUpdate ( ) ;
System . out. println ( i) ;
preparedStatement. close ( ) ;
}
}
配置文件方式
public class DruidTest2 {
public static void main ( String [ ] args) throws Exception {
Properties properties = new Properties ( ) ;
properties. load ( new FileReader ( "druid.properties" ) ) ;
DataSource ds = DruidDataSourceFactory . createDataSource ( properties) ;
Connection conn = ds. getConnection ( ) ;
String sql = "select * from bank;" ;
PreparedStatement preparedStatement = conn. prepareStatement ( sql) ;
ResultSet resultSet = preparedStatement. executeQuery ( ) ;
while ( resultSet. next ( ) ) {
String string = resultSet. getString ( "username" ) ;
System . out. println ( string) ;
}
preparedStatement. close ( ) ;
resultSet. close ( ) ;
}
}
Druid配置文件
#基本配置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///my_database
username=root
password=123456
#可选配置
filters=stat
initialSize=2
maxActive=300
maxWait=60000
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
poolPreparedStatements=false
maxPoolPreparedStatementPerConnectionSize=200
DBUtils工具类
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
dbUtils 封装了操作对象 帮我们把查询的数据封装好
连接池->连接对象->操作对象
使用步骤:
1.导入jar包(commons-dbutils-1.4.jar)
2.创建一个queryrunner类
queryrunner作用:操作sql语句
构造方法:
new QueryRunner(Datasource ds);
3.编写sql
4.执行sql
query(..):执行查询操作
update(...):执行增删改操作
//传入连接池
通过连接池传入
new QueryRunner(连接池);
String sql = "insert into bank values(?,?);";
int i = queryRunner.update(sql,"qweqwe",15000);//增删改
判断if(i>0)
String sql1 = "select * from bank where money=?;";
queryRunner.query(sql1, new BeanHandler<User>(User.class),9999);//查
//new BeanHandler<Users>(Users.class) 一个对象
String sql2 = "select * from bank;";
queryRunner.query(sql2, new BeanListHandler<User>(User.class));//查
//new BeanListHandler<Users>(Users.class) 多个对象
增删改(DML)
public class JDBC_DBUtils1 {
public static void main ( String [ ] args) throws Exception {
Properties properties = new Properties ( ) ;
properties. load ( new FileReader ( "dbcp.properties" ) ) ;
DataSource ds = BasicDataSourceFactory . createDataSource ( properties) ;
QueryRunner queryRunner = new QueryRunner ( ds) ;
String sql = "insert into bank values(?,?);" ;
int i = queryRunner. update ( sql, "qweqwe" , 15000 ) ;
if ( i > 0 ) {
System . out. println ( "修改成功" ) ;
} else {
System . out. println ( "修改失败" ) ;
}
}
}
查(DQL)
public class JDBC_DBUtils2 {
public static void main ( String [ ] args) throws Exception {
Properties properties = new Properties ( ) ;
properties. load ( new FileReader ( "dbcp.properties" ) ) ;
DataSource ds = BasicDataSourceFactory . createDataSource ( properties) ;
QueryRunner queryRunner = new QueryRunner ( ds) ;
System . out. println ( "====查询一条语句====" ) ;
String sql1 = "select * from bank where money=?;" ;
User user = queryRunner. query ( sql1, new BeanHandler < User > ( User . class ) , 9999 ) ;
System . out. println ( user) ;
System . out. println ( "====查询多条语句====" ) ;
String sql2 = "select * from bank;" ;
List < User > list = queryRunner. query ( sql2, new BeanListHandler < User > ( User . class ) ) ;
for ( User user1 : list) {
System . out. println ( user1) ;
}
}
}