1 再谈事务
一个事务本质在于:其内部的功能要么都成功,要么都失败!
之前的blog中谈过事务,那一篇是在Sequel Pro中测试的,此处重点谈论如何用Idea来操作事务!
ACID原则
原子性:要么全部完成,要么都不完成
一致性:总数不变
隔离性:多个进程互不干扰
持久性:一旦提交不可逆,持久化到数据库内
隔离性的问题:
脏读:一个事务读取了另一个没有提交的事务
不可重复读:在同一个事务内,重复读取表中的数据,数据发生了改变
幻读(虚读):在一个事务内,读取到了别人插入的数据,导致前后读出来的结果不一致。
代码实现
- 开启事务
conn.setAutoCommit(false);
- 一组业务执行完毕,提交事务!
- 可以在catch语句中显示的定义回滚语句,但默认失败就会回滚
源码:(其中用到上篇博客中的封装类)
package com.demut.demo04;
import com.demut.demo02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestTransaction {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false); // 关闭自动提交-->开启事务
String sql1 = "update account set money = money-100 where name = 'A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
//int x = 1/0; //可以故意设置报错
String sql2 = "update account set money = money+100 where name = 'B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
//业务完毕,提交事务
conn.commit();
System.out.println("成功!");
} catch (SQLException e) {
try {
conn.rollback(); //如果失败则回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
2 数据库连接池
为什么需要用到数据库连接池呢?其原因在于我们每次连接数据库都经历了数据库连接 --- 执行完毕 --- 释放
的过程,而其中的连接 --- 释放
尤其浪费系统资源。
举个例子:我们如果要去银行办理事务,倘若银行采用来一个人 --- 开门 --- 办理结束 --- 关门 --- 来一个人...
的模式,十分耗费时间以及精力。所以我们开一次门,设置特定的业务员,使用业务员来对接每一位来的用户,这样就能很好的减少资源浪费,当然每位业务员也有自己的精力,所以有最大连接数,最小连接数,以及常用连接数!类似这样的思路,我们引入数据库的池化技术。
池化技术:准备一些预先的资源,过来就连接预先准备好的
--- 开门 --- 业务员: 等待 --- 服务 --
设置常用连接数:比如10个
设置最小连接数:比如10个
设置最大连接数:业务最高承载上限,例如15个
如果达到最大连接数,排队等待,如果等待超时(例如100ms)就提示其异常。
其本质在于:编写连接池,实现一个接口DataSource
开源数据源实现
现在的主流数据源有:DBCP、C3P0、Druid(阿里巴巴)
使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了!下面开始介绍使用方法:
2.1 DBCP
需要用到dbcp-jar包 :下载链接
pool-jar包:下载链接
如果导入后测试出现NoClassDefFoundError
错误,则还需导入logging.jar包:下载链接
随后将包导入到idea的lib目录中,如果不知道如何导入,见我的另一篇博客:链接
另外需要添加一个配置文件:dbcp.properties
,注意直接将该文件创建在src目录下!
dbcp.properties文件代码如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#<!-- 连接编码设置 -->
connectionProperties=useUnicode=true;characterEncoding=utf8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
编写JdbcUtils_DBCP
工具类:
package com.demut.demo05.utils;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils_DBCP {
private static DataSource dataSource = null;
//加载数据库驱动
static {
try {
//获取文件流
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in); //加载流
//创建数据源 工厂模式 --> 创建
dataSource = BasicDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection(); //从数据源中获取连接
}
//释放连接资源
public static void free(Connection conn, Statement st, ResultSet rs){
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st !=null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn !=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
另编写测试类,测试使用DBCP连接:
package com.demut.demo05;
import com.demut.demo05.utils.JdbcUtils_DBCP;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
public class TestDBCP {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
conn = JdbcUtils_DBCP.getConnection();
//区别
//使用?占位符代替参数
String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
pst = conn.prepareStatement(sql); //预编译SQL,先写sql,不执行
//手动给参数赋值:
pst.setInt(1,6); //此处的1表示第一个问好
pst.setString(2,"Jever");
pst.setString(3,"111111");
pst.setString(4,"[email protected]");
//注意: sql.Date 数据库 java.sql.Date()
// util.Date Java new Date().getTime() 获得时间戳
pst.setDate(5,new java.sql.Date(new Date().getTime()));
int i = pst.executeUpdate();
if (i > 0) {
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils_DBCP.free(conn,pst,rs);
}
}
}
/*测试结果:
插入成功!
*/
2.2 C3P0
需要下载的jar包:c3p0-x.x.x.x、mchange-commons-java-x.x.xx
同样需要导入jar包,同上!
创建配置文件c3p0-config.xml
<c3p0-config>
<!--使用默认的配置读取数据库连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&characterEncoding=utf8</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池参数 -->
<!--初始化申请的连接数量-->
<property name = "acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<!--最小的连接数量-->
<property name = "minPoolSize">5</property>
<!--最大的连接数量-->
<property name="maxPoolSize">20</property>
<!--超时时间-->
<property name="checkoutTimeout">3000</property>
</default-config>
<!--
C3P0的命名配置
-->
<named-config name="MySQL">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&characterEncoding=utf8</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池参数 -->
<!--初始化申请的连接数量-->
<property name = "acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<!--最小的连接数量-->
<property name = "minPoolSize">5</property>
<!--最大的连接数量-->
<property name="maxPoolSize">20</property>
<!--超时时间-->
<property name="checkoutTimeout">3000</property>
</named-config>
</c3p0-config>
编写JdbcUtils_C3P0
工具类:
package com.demut.demo05.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils_C3P0 {
private static DataSource dataSource = null;
static {
try {
//创建数据源 工厂模式 --> 创建
dataSource = new ComboPooledDataSource("MySQL"); //配置文件写法
}catch (Exception e){
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st !=null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn !=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
另编写测试类,测试使用C3P0连接:
package com.demut.demo05;
import com.demut.demo05.utils.JdbcUtils_C3P0;
import com.demut.demo05.utils.JdbcUtils_DBCP;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
public class TestC3P0 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
conn = JdbcUtils_C3P0.getConnection(); //原来是自己实现的,现在是用别人实现的
//区别
//使用?占位符代替参数
String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
pst = conn.prepareStatement(sql); //预编译SQL,先写sql,不执行
//手动给参数赋值:
pst.setInt(1,5); //此处的1表示第一个问好
pst.setString(2,"Jever");
pst.setString(3,"111111");
pst.setString(4,"[email protected]");
//注意: sql.Date 数据库 java.sql.Date()
// util.Date Java new Date().getTime() 获得时间戳
pst.setDate(5,new java.sql.Date(new Date().getTime()));
int i = pst.executeUpdate();
if (i > 0) {
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils_C3P0.release(conn,pst,rs);
}
}
}
/*运行结果:
四月 19, 2020 4:15:24 下午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
四月 19, 2020 4:15:25 下午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.2.1 [built 20-March-2013 10:47:27 +0000; debug? true; trace: 10]
四月 19, 2020 4:15:25 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 3000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> MySQL, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge0yxa914df8d71bm1ykq|2e5d6d97, idleConnectionTestPeriod -> 0, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&characterEncoding=utf8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 5, numHelperThreads -> 3, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
插入成功!
说明: 上部分为日志,可以自行修改,最后一行为结果!
*/
Druid我们随后再说!(此处挖个坑)
2.3 结论
无论使用什么数据源,本质都是一样的,DataSource接口不会变,方法就不会变!
写在最后
Keep your servant also from willful sins; may they not rule over me. Then I will be blameless, innocent of great transgression. (Psalms 19 : 13)
To Demut!