文章目录
1-事务介绍与简单使用
事务 - Transaction
其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败,则所有的数据都回归到最初的状态(回滚)
为什么要有事务?
为了确保逻辑的成功 例子:银行的转账
开启事务命令:
start transaction;
提交或者回滚事务命令:
commit; 提交事务,数据将会写到磁盘上的数据库
rollback; 数据回滚,回到最初的状态
自动提交命令:
set autocommit = off;
autocommit会降低运行效率并且容易发生数据错误,若异常则后续代码无法执行。关闭autocommit后,若要使事务执行则commit,否则就回滚rollback
设置隔离级别命令:
set session transaction isolation level (级别);
事务在Java中的简单使用:
public void testTransaction(){
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);
//扣钱, 扣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) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtil.release(conn, ps, rs);
}
}
注意:若不使用事务(autocommit开着),则执行事务逻辑时某处出现的异常可能导致整个数据出错
2-事务 特性(ACID)和安全隐患
- 原子性
> 指的是 事务中包含的逻辑,不可分割 - 一致性
> 指的是 事务执行前后.数据完整性 - 隔离性
> 指的是 事务在执行期间不应该受到其他事务的影响 - 持久性
> 指的是 事务执行成功,那么数据应该持久保存到磁盘上
不考虑隔离级别设置,那么会出现以下问题
-
脏读
> 一个事务读到另外一个事务还未提交的数据
-
不可重复读
> 一个事务读到了另外一个事务提交的数据,造成了前后两次查询结果不一致 -
幻读
> 一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致
事务的可串行化:
如果有一个连接的隔离级别设置为了串行化,那么谁先打开了事务,谁就有了先执行的权利,谁后打开事务,谁就只能得着,等前面的那个事务,提交或者回滚后,才能执行
- 按效率划分,从高到低
> 读未提交 > 读已提交 > 可重复读 > 可串行化 - 按拦截程度,从高到底
> 可串行化 > 可重复读 > 读已提交 > 读未提交
事务的丢失更新现象:
悲观锁和乐观锁:
1、可以在查询的时候加入for update,表示悲观锁
2、乐观锁要求程序员自己规定标识
事务小结:
1、需要掌握在Java中使用事务
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
2、需要熟悉了解事务的安全隐患和隔离级别
3、mySql默认的隔离级别是可重复读。Oracle是读已提交
4、需要会在数据出现异常时学会排错
3-数据库连接池介绍与简单使用
前提引入:如果每个程序连接数据库都要创建一个数据库对象,那么会比较消耗性能。所以我们一开始直接在内存中使用一块集合空间存放多个连接对象,如果有程序需要连接对象则给一个,使用完毕之后回收,这样的话不仅能够循环利用而且还能够提升效率。
数据库连接池的搭建分析:
1、先创建X个连接
2、要用的程序通过getConnection来获取连接
3、用完之后,使用addBack来归还连接
4、如果连接池中不够了,则扩容
5、list.remove(0)移出list中的第一个,移出后第二个默认到第一个,还是remove(0)
数据库连接池的简单搭建:
这里暂时创建一个(JDBCUtil)对象类,用于声明连接对象。接着自己手动创建一个连接池对象,具体的主要代码如下↓
List<Connection> connList = new ArrayList<Connection>();
public List<Connection> getConnList() {
return connList;
}
public void setConnList(List<Connection> connList) {
this.connList = connList;
}
public MyDataSource() {
for (int i = 0; i < 10; i++) {
Connection conn = JDBCUtil_old.getConn();
connList.add(conn);
}
}
//目前使用到的方法只有getConnection
@Override
public Connection getConnection() throws SQLException {
if(connList.size()==0){
for (int i = 0; i < 5; i++) {//一次加5个
Connection conn = JDBCUtil_old.getConn();
connList.add(conn);
}
}
Connection conn = new ConnectionWrap(connList.remove(0),this.connList);//删除并返回Connection
System.out.println("分配一个Connection之后的list大小:"+connList.size());
return conn;
}
//使用完毕之后,利用addBack方法,回收连接
public void addBack(Connection conn){
connList.add(conn);
System.out.println("还原一个Connection之后的list大小:"+connList.size());
}
自定义数据库连接池的问题:
1、需要额外记住addBack方法
2、单例模式的使用,创建多个数据库连接池,占用内存高
3、由于addBack方法接口中没有,无法面向接口编程
解决自定义数据库连接池问题的重点在于把addBack换成接口已有的方法:
1、使用装饰者模式
2、使用动态代理
4-数据库连接池 装饰者模式
装饰者模式改进自定义数据库连接池主要代码:
Connection conn = null;
List<Connection> connList = null;
public ConnectionWrap(Connection conn,List<Connection> connList) {
this.conn = conn;
this.connList = connList;
}
@Override
public void close() throws SQLException {
connList.add(conn);
System.out.println("还原一个Connection之后的list大小:"+connList.size());
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
conn.setAutoCommit(autoCommit);
}
@Override
public void commit() throws SQLException {
conn.commit();
}
@Override
public void rollback() throws SQLException {
conn.rollback();
}
5-数据库连接池DBCP
DBCP连接池的apache上的一个Java连接池项目。
1、使用直连的方式获取连接:
//1. 构建数据源对象
BasicDataSource dataSource = new BasicDataSource();
//连的是什么类型的数据库, 访问的是哪个数据库 , 用户名, 密码。。
//jdbc:mysql://localhost/bank 主协议:子协议 ://本地/数据库
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/bank");
dataSource.setUsername("root");
dataSource.setPassword("root");
//2. 得到连接对象
conn = dataSource.getConnection();
2、通过配置文件、工厂获取连接:
首先导入两个jar包(一个pool,一个dbcp),一个properties文件
其次建立工厂,通过工厂来获取连接,代码如下:
BasicDataSourceFactory bdsf = new BasicDataSourceFactory();
//bdsf不能生成自定义的DataSource
//MyDataSource ds = null;
DataSource ds = null;
Connection conn = null;
InputStream in = null;
Properties pro = null;
try {
pro = new Properties();
in = new FileInputStream("src/dbcpconfig.properties");
pro.load(in);
ds = bdsf.createDataSource(pro);
conn = ds.getConnection();
}
6-数据库连接池C3P0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
1、使用直连的方式获取连接:
//1. 创建datasource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2. 设置连接数据的信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");
//忘记了---> 去以前的代码 ---> jdbc的文档
dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
dataSource.setUser("root");
dataSource.setPassword("root");
//3. 得到连接对象
conn = dataSource.getConnection();
2、通过配置文件获取连接:
ComboPooledDataSource ds = new ComboPooledDataSource(); //后有参数可以选择数据库类型
Connection conn = null;
conn = ds.getConnection();
//配置文件
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/bank?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">qq</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>
数据库连接池总结:
1、配置文件中的url如果就写到目标数据库名则会报错,原因是没有规定使用的解码器
2、配置文件中写了解码器的代码但没有使用转义字符,会报错"characterEncoding" must end with the ‘;’ delimiter;原因是xml不能解析’&’,所以把‘&’改成"& ;" 即可
3、Tried to check-in a foreign resource! - 线程无法正常运行,不要手动关闭ds
4、ComboPooledDataSource源代码中自动会帮助获取连接信息,所以要写好配置文件
7-DBUtils以及Handler
利用DBUtils进行增删改查操作,这里主要利用到QueryRunner对象
//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);
这里要单独把查找功能写出来,因为查找的时候有利用到一个Handler集合对象
1、我们直接new接口的匿名实现类(ResultSetHandler)
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());
//ResultSetHandler为最大的接口,所以要使用必须自行定义接口方法,所以@Override
2、直接使用已经写好的实现类(BeanListHandler)
ComboPooledDataSource ds = new ComboPooledDataSource(); //后有参数可以选择数据库类型
QueryRunner eq = new QueryRunner(ds);
try {
//查找
List<Account> accounts = eq.query("select * from account", new BeanListHandler<Account>(Account.class));
for (Account account : accounts) {
String name = account.getName();
int money = account.getMoney();
System.out.println("Account:"+name+"-"+money);
}
} catch (SQLException e) {
e.printStackTrace();
}
DBUtils与Handler总结:
ResultSetHandler 常用的实现类
以下是使用频率从高往低排序结果:
------------------------------------------✈
BeanHandler, 查询到的单个数据封装成一个对象
BeanListHandler, 查询到的多个数据封装 成一个List<对象>
------------------------------------------🚑
ArrayHandler, 查询到的单个数据封装成一个数组
ArrayListHandler, 查询到的多个数据封装成一个集合 ,集合里面的元素是数组。
------------------------------------------⛵
MapHandler, 查询到的单个数据封装成一个map
MapListHandler,查询到的多个数据封装成一个集合 ,集合里面的元素是map。
------------------------------------------🚲
ColumnListHandler
KeyedHandler
ScalarHandler
自我总结
这部分的重点就是要学会使用事务,并且会解决关于数据库连接出现的问题。
其次,我个人用C3P0用得多,学到目前阶段感觉C3P0是真的很方便,要好好掌握。
最后,熟练使用BeanListHandler,这个框架很好用
——— 给自己一个恰如其分的自信。