文章目录
事务
- Transaction,其实指的是一组操作,里面包含多个单一的逻辑。只要有一个逻辑没有执行成功,那么就算失败。所有的数据都回归到最初的状态(回滚)
- 为什么要有事务:为了确保逻辑的成功。(银行的转账)
使用代码方式演示事务
package com.java_01;
import java.sql.*;
public class MysqlTest {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
String uri = "jdbc:mysql://localhost:3306/bank";
String user = "root";
String password = "root";
Connection conn = null;
PreparedStatment ps = null;
try {
conn = DriverManager.getConnection(uri,user,password);
String sql = "update account set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
conn.setAutoCommit(false);
//扣除100元
ps.setInt(1,100);
ps.setInt(2,1);
ps.executeUpdate();
//加上100元
ps.setInt(1,-100);
ps.setInt(2,2);
ps.executeUpdate();
conn.commit();
conn.close();
} catch(SQLException e) {
try {
conn.rollback();
} catch(SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
事务的特性
- 原子性:指的是事务包含的逻辑,不可分隔
- 一致性:事务执行前后,数据完整性
- 隔离性:事务在执行期间不应该收到其它事务的影响
- 持久性:事务执行成功,那么数据应该持久保存到磁盘上
事务的安全隐患
不考虑隔离级别设置,那么会出现一下问题
- 读:脏读,不可重复度,幻读
脏读:一个事务读到另外一个事务还未提交的数据
不可重复读:这个隔离级别能够屏蔽脏读的现象,但是引发了另外一个问题,不可重复读
幻读:一个事务读到了另外一个事务已经提交的插入的数据,导致多次查询结果不一致
- 写:丢失更新
有两种方式:
- 悲观锁,可以在查询的时候,加入for update
- 乐观锁,要求程序员自己控制
隔离级别:
- Read Uncommiitted[读未提交]
- Read Committed[读已提交]
- Repeatable Read[重复读]
- Serializable[可串行化]:如果一个连接的隔离级别为了串行化,那么谁先打开了事务,谁就有了先执行的权力,谁后打开事务,谁就只能等着,待前面的那个事务提交或者回滚后,才能执行。但是这种隔离级别一般比较少用。容易造成性能上的问题。效率比较低
- 按照效率划分,从高到低:读未提交>读已提交>可重复度>可串行化
- 按照拦截程序,从高到低:可串行化>可重复读>读已提交>读未提交
数据库连接池
简单介绍
- 数据库的连接对象创建工作,比较消耗性能
- 一开始在内存中开辟一块空间(集合),一开始先往池子里面放置多个连接对象。后面需要连接的话直接从池子中去取。不要去自己创建连接了。使用完毕,要记得归还连接。确保连接对象能循环利用
解决自定义数据库连接池出现的问题
- 问题:由于多了一个addBack方法,所以使用这个连接池的地方,需要额外记住这个方法,并且还不能面向接口编程
- 解决方法:我们打算修改接口中的close方法。原来的Connection对象的close方法,是真的关闭连接。现在我们修改这个close方法,以后再调用close,并不是真的关闭,而是归还连接对象
如何扩展某一个方法
原有的方法逻辑,不是我们想要的。想修改自己的逻辑
- 直接改源码,无法实现
- 继承,必须得知道这个接口的具体实现是谁
- 使用修饰者模式
开源连接池
- DBCP:(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放和断开
- 不使用配置文件:
public class DBCPDemo {
Connection conn = null;
PreparedStatement ps = null;
@Test
public void testDBCP() {
try {
//构建数据源对象
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/bank");
dataSource.setUsername("root");
dataSource.setPassword("root");
//得到连接对象
conn = dataSource.getConnection();
String sql = "insert into account values(null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "admin");
ps.setInt(2, 100);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 使用配置文件方式
public class DBCPDemo02 {
Connection conn = null;
PreparedStatement ps = null;
@Test
public void testDBCP02() {
try {
BasicDataSourceFactory factory = new BasicDataSourceFactory();
Properties properties = new Properties();
InputStream is = getClass().getClassLoader().getResourceAsStream("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, "wangwu");
ps.setInt(2, 999);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- C3P0:是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
- 不适用配置文件方式
public class C3P0Demo {
@Test
public void testC3P0() {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/bank");
dataSource.setUser("root");
dataSource.setPassword("root");
Connection conn = dataSource.getConnection();
String sql = "update account set money = money - ? where id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
} catch (PropertyVetoException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 使用配置文件
public class C3P0Demo02 {
@Test
public void testC3P0() {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
Connection conn = dataSource.getConnection();
String sql = "update account set money = money - ? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 100);
ps.setInt(2, 2);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//注意:需要在src下面添加c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/bank</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="automaticTestTable">con_test</property>
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</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>
- Druid:数据库连接池实现技术,由阿里巴巴提供的
使用步骤:
- 导入jar包 druid-1.0.9.jar
- 定义配置文件:是properties形式的,可以叫任意名称,可以放在任意目录下
- 加载配置文件
- 获取数据库连接池对象:通过工厂类来获取,DruidDataSourceFactory
- 获取连接:getConnection
public static void main(String[] args) throws Exception {
//加载druid.properties文件
Properties prop = new Properties();
prop.load(new FileReader(DruidDemo.class.getClassLoader().getResource("druid.properties").getPath()));
//prop.load(DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"));
//创建数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取连接数据库对象
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
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 = ?",10000,6);
查询
- 直接new接口的匿名实现类
String sql = "select * from account where id = ?";
Account account = queryRunner.query(sql, 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;
}
}, 2);
System.out.println(account);
- 直接使用框架已经写好的实现类
- 查询单个对象
String sql = "select * from account where id = ?";
Account account = queryRunner.query(sql,new BeanHandler<Account>(Account.class), 4);
System.out.println(account);
- 查询多个对象
String sql = "select * from account";
List<Account> list = queryRunner.query(sql,new BeanListHandler<Account>(Account.class));
for (Account account : list) {
System.out.println(account);
}
ResultSetHandler常用的实现类
一下两个是使用频率最高的
BeanHandler:查询到的单个数据封装成一个对象
BeanListHandler:查询到的多个数据封装成一个List<对象>
//------------------------------------------------
ArrayHandler:查询到的单个数据封装成一个数组
ArrayListHandler:查询到的多个数据封装成一个集合,集合里面的元素是数组
//------------------------------------------------
MapHandler:查询到的单个数据封装成一个map
MapListHandler:查询到的多个数据封装成一个集合,集合里面的元素是map
//------------------------------------------------
ColumnListHandler
KeyedHandler
ScalarHandler