1).关闭自动提交的设置 conn.setAutoCommit(false)
2).提交事务 conn.commit
3).回滚事务 conn.rollback
package web.stu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import web.stu.util.JDBCUtil;
public class Transaction {
public static void main(String[] args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtil.getConn();
//连接,事务默认就是自动提交的。 关闭自动提交。
conn.setAutoCommit(false);
String sql = "update account set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
//int a = 10 /0 ;
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.release(conn, ps, rs);
}
}
}
- 原子性:事务中包含的逻辑不可分割。
- 一致性:事务执行的前后,数据的完整性保持一致。
- 隔离性:事务在执行期间不受到其他事务的影响。
- 持久性:事务执行成功,则数据应该持久保存到磁盘上。
- 读产生的问题:
- 读脏数据:一个事务读到另外一个事务还未提交的数据。
- 不可重复读:一个事务读到另外一个事务提交的数据,造成前后两次查询结果不一致。
- 幻读:一个事务读到了另一个事务已提交的插入数据,导致多次查询结果不一样。
- 隔离级别:
- 读未提交:会引起脏读的问题。
- 读已提交:能解决脏读的问题,但引发不可重复读。
- 可重复读:能解决脏读,不可重复读的问题,但是不能解决幻读。
- 可串行化:能解决脏读,不可重复读,幻读的问题。
mysql默认的隔离级别是可重复读
Oracle默认的隔离级别是读已提交
- 写产生的问题:丢失更新
- 解决丢失更新的方法:
- 悲观锁:可以在查询的时候加入for update
- 乐观锁:要求程序员自己控制
1)数据库的连接对象创建工作,比较消耗性能。
2)一开始先在内存中开辟一块空间(集合),一开始先往里面放置多个连接对象。后面如果需要连接, 直接从里面取,不要自己创建连接。使用完毕后,要归还连接,确保连接对象可以循环利用。
- 先往池子里面放10个连接
- 开始创建10个连接
- 来的程序通过getConnection获取连接
- 用完之后,使用back归还连接
- 扩容
- 使用这种方法(MyDataSource.java)会产生的问题:
- 需要额外记住back归还方法。
- 没法实现单例模式,导致不断地new对象。
- 无法面向接口编程,因为dataSource接口里面没有定义back方法,导致没法调用。
MyDataSource.java
package util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource implements DataSource{
//先往池子里面放10个连接
List list =new ArrayList();
public MyDataSource(){
for (int i = 0; i < 10; i++) {
Connection conn=JDBCUtil.getConn();
list.add(conn);
}
}
//连接池对外公布的获取连接的方法
@Override
public Connection getConnection() throws SQLException {
//来拿连接时,先判断池子有没有连接,如果没有就扩容
if(list.size()==0){
for (int i = 0; i < 10; i++) {
Connection conn=JDBCUtil.getConn();
list.add(conn);
}
}
//移除的是集合中的第一个元素
Connection conn=list.remove(0);
return conn;
}
//用完之后,归还到池子里
public void back(Connection conn){
list.add(conn);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
return null;
}
}
TestPool.java
package util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
public class TestPool {
@Test
public void testPool(){
Connection conn=null;
PreparedStatement ps=null;
MyDataSource dataSource=new MyDataSource();
try {
conn=dataSource.getConnection();
String sql="insert into account values (null,'qq',2000)";
ps=conn.prepareStatement(sql);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
//归还连接
dataSource.back(conn);
}
}
}
- 解决方法:修改接口中的close方法。原来的Connection对象的close方法是真的关闭连接,改成以后关闭时,是一个归还连接的方法。
- 如何扩展一个方法:
- 直接改源码,无法实现。
- 使用继承,无法知道实现Connection接口的类,所以也不行。
- 使用装饰者模式。(在这使用)
- 动态代理。
使用装饰者模式解决后的代码~
MyDataSource2.java
package util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource2 implements DataSource{
//先往池子里面放10个连接
List list =new ArrayList();
public MyDataSource2(){
for (int i = 0; i < 10; i++) {
Connection conn=JDBCUtil.getConn();
list.add(conn);
}
}
//连接池对外公布的获取连接的方法
@Override
public Connection getConnection() throws SQLException {
//来拿连接时,先判断池子有没有连接,如果没有就扩容
if(list.size()==0){
for (int i = 0; i < 10; i++) {
Connection conn=JDBCUtil.getConn();
list.add(conn);
}
}
//移除的是集合中的第一个元素
Connection conn=list.remove(0);
//conn.close(); //直接调用自己扩展的,直接可以归还
//在把这个对象抛出去的时候,对这个对象进行包装
Connection connection=new ConnectionWrap(conn,list);
return connection;
}
//用完之后,归还到池子里
public void back(Connection conn){
list.add(conn);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
return null;
}
}
ConnectionWrap.java需要实现Connection接口,重写close方法,因为代码太长,所以只贴出部分代码截图
测试代码TestPool2.java
package util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
public class TestPool2 {
@Test
public void testPool(){
Connection conn=null;
PreparedStatement ps=null;
MyDataSource dataSource=new MyDataSource();
try {
conn=dataSource.getConnection();
String sql="insert into account values (null,'qq',2000)";
ps=conn.prepareStatement(sql);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtil.release(conn, ps);
}
}
}
使用到之前的JDBCUtil.java代码
package util;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtil {
static String driverClass = null;
static String url = null;
static String name = null;
static String password= null;
static{
try {
//1. 创建一个属性配置对象
Properties properties = new Properties();
InputStream is = new FileInputStream("jdbc.properties"); //Java工程里可以直接new来加载配置文件
//使用类加载器,去读取src底下的资源文件。 后面在servlet,web工程需要使用类加载器来加载配置文件
//InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
//导入输入流。
properties.load(is);
//读取属性
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
name = properties.getProperty("name");
password = properties.getProperty("password");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接对象
* @return
*/
public static Connection getConn(){
Connection conn = null;
try {
Class.forName(driverClass);
//静态代码块 ---> 类加载了,就执行。 java.sql.DriverManager.registerDriver(new Driver());
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//DriverManager.getConnection("jdbc:mysql://localhost/test?user=monty&password=greatsqldb");
//2. 建立连接 参数一: 协议 + 访问的数据库 , 参数二: 用户名 , 参数三: 密码。
conn = DriverManager.getConnection(url, name, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 释放资源
*/
public static void release(Connection conn , Statement st , ResultSet rs){
closeRs(rs);
closeSt(st);
closeConn(conn);
}
public static void release(Connection conn , Statement st){
closeSt(st);
closeConn(conn);
}
private static void closeRs(ResultSet rs){
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
rs = null;
}
}
private static void closeSt(Statement st){
try {
if(st != null){
st.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
st = null;
}
}
private static void closeConn(Connection conn){
try {
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
conn = null;
}
}
}
- 创建一个属性配置对象
Properties properties = new Properties();
InputStream is = new FileInputStream("jdbc.properties"); //Java工程里可以直接new来加载配置文件
使用类加载器,去读取src底下的资源文件。 后面在servlet,web工程需要使用类加载器来加载配置文件
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
导入输入流。
properties.load(is);