文章目录
事务
一、简述
👉点击👈,查看第 Ⅴ 点。
二、JDBC事务的操作0
连接对象.setAutoCommit(false):手动提交
连接对象.commit():提交
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
public class Transfer {
@Test
public void change() {
//Sael.connect()Sael中封装的注册、连接数据库
Connection conn = Sael.connect();
PreparedStatement prst = null;
String sql1 = "update countmoney set mmoney=mmoney+1200 where mname='李四'";
String sql2 = "update countmoney set mmoney=mmoney-1200 where mname='张三'";
try {
//设置为手动提交
conn.setAutoCommit(false);
prst = conn.prepareStatement(sql1);
int i = prst.executeUpdate();
System.out.println("收钱:" + i);
prst = conn.prepareStatement(sql2);
int j = prst.executeUpdate();
System.out.println("转钱:" + j);
conn.commit();
} catch (SQLException e) {
//如果实行过程中没有异常发生,那么就正常提交。否则,撤回(回滚)
try {
System.out.println("转账失败,回滚开始");
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
System.out.println("回滚结束");
e.printStackTrace();
}finally{
//Sael中封装的释放资源的方法
Sael.closeAll(null, prst, conn);
}
}
}
PS:Sael见Java代码操作数据库(上)👉点击👈
三、事务的隔离级别,略
引用——
事务特性:
事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。
原子性:
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做。
一致性:
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。比如,当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统在运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
隔离性:
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性:
指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
事务隔离级别:
以上四种隔离级别按照从低到高的顺序排列为:
Read uncommitted <Read committed <Repeatable read <Serializable
1、Read uncommitted:未提交读,就是一个事务可以读取另一个未提交事务的数据。
事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的账户,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
那怎么解决脏读呢?使用Read committed(已提交读),能解决脏读问题。
2、Read committed:已提交读,就是一个事务要等另一个事务提交后才能读取数据。
事例:程序员拿着信用卡去享受生活(假设卡里只有3.6万),当他买单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候,程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
那怎么解决可能的不可重复读问题?使用Repeatable read (可重复读)。
3、Repeatable read:可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他买单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
什么时候会出现幻读?
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
那怎么解决幻读问题?使用Serializable(可串行化)。
4、Serializable 序列化
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
数据库连接池
一、连接池简介
连接池优势:
1、程序启动时,就在池子中放入一定数量的连接,不需要用户发出请求时再创建,减轻服务器压力。
2、用户需要操作数据库时,直接从池子中拿一个空闲连接即可,用完了再还回来供下一个用户使用,实现了连接的循环利用。
3、用户获取连接时,池子中已经没有空闲连接。如果池子容量未达上限,创建一批新的连接放进去供用户使用;如果池子容量已达上限,用户就必须等待其他用户归还后再使用。
4、如果池子中有长期处于空闲状态的连接,关闭一批空闲连接,优化系统性能。
二、手动实现初·连接池
代码
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class DemoPool {
//创建列表,用于存储连接对象
private static List<Connection> pool = new ArrayList<Connection>();
//创建5个连接,只执行一次
static {
for(int i=1;i<6;i++) {
Connection conn = Sael.connect();
pool.add(conn);
System.out.println(conn + "这是连接池中被创建的第" + i + "个连接");
}
}
//提供外部获取连接的方法
public static Connection getconn() {
//外部获取一个,连接池中就要少一个(remove有返回值的)
Connection conn = pool.remove(0);
System.out.println("得到一个连接,还有" + pool.size() + "个连接");
return conn;
}
//提供外部归还连接的方法
public static void returnconn(ResultSet rs,Connection conn,PreparedStatement prst) {
try {
if(rs !=null)
rs.close();
if(prst !=null)
prst.close();
if(conn !=null) {
pool.add(conn);
System.out.println("已经归还" + conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
测试
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
public class TestPool {
@Test
public void usepool() {
Connection conn = DemoPool.getconn();
PreparedStatement prst = null;
ResultSet rs = null;
String sql = "select * from countmoney";
try {
prst = conn.prepareStatement(sql);
rs = prst.executeQuery();
while(rs.next()) {
System.out.println(rs.getString("mname") + "\t" + rs.getDouble("mmoney"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
DemoPool.returnconn(null, conn, prst);
}
}
}
控制台
com.mysql.cj.jdbc.ConnectionImpl@6a2b953e这是连接池中被创建的第1个连接
com.mysql.cj.jdbc.ConnectionImpl@2f465398这是连接池中被创建的第2个连接
com.mysql.cj.jdbc.ConnectionImpl@623a8092这是连接池中被创建的第3个连接
com.mysql.cj.jdbc.ConnectionImpl@3098cf3b这是连接池中被创建的第4个连接
com.mysql.cj.jdbc.ConnectionImpl@6a03bcb1这是连接池中被创建的第5个连接
得到一个连接,还有4个连接
张三 1000.0
李四 1000.0
已经归还com.mysql.cj.jdbc.ConnectionImpl@6a2b953e
三、DBCP连接池
步骤
1、将jar导入项目
2、创建配置文件,一般放到src下。注意【因为是自动读取配置文件,所以,不能改动key(等号前面的值)】
3、初始化数据源(DataSource),即初始化连接池
4、提供外部访问获取连接的方法
5、提供外部访问归还的方法(仍然使用close,但是DBCP中的close是归还而非关闭(不同实现类))
配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/money?serverTimezone=GMT%2B8&characterEncoding=UTF-8
username=root
password=******
initialSize=10
maxActive=30
封装
package com.demo.day09;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
public class DemoDBCP {
private static DataSource ds = null;
static {
Properties pro = new Properties();
try {
//读取文件,并转为properties
pro.load(new FileInputStream("src/dbcp.properties"));
//初始化数据源
ds = BasicDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//提供供外部获取连接的方法
public static Connection getconn() {
Connection conn = null;
try {
conn = ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//提供归还连接的方法
public static void returnconn(Connection conn,PreparedStatement prst,ResultSet rs) {
try {
if(rs != null)
rs.close();
if(prst != null)
prst.close();
if(conn != null)
//不同实现类,这里的实现是归还
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
测试
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
public class TestDBCP {
@Test
public void usepool() {
Connection conn = DemoDBCP.getconn();
PreparedStatement prst = null;
ResultSet rs = null;
String sql = "select * from countmoney";
try {
prst = conn.prepareStatement(sql);
rs = prst.executeQuery();
while(rs.next()) {
System.out.println(rs.getString("mname") + "\t" + rs.getDouble("mmoney"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
DemoDBCP.returnconn(conn, prst, rs);
}
}
}
控制台
张三 1000.0
李四 1000.0
四、【c3p0连接池0 】
0、导包
1、配置文件名(c3p0.properties)不能变
2、配置文件只能放在src下面
3、配置文件中key值不能改动
配置文件
c3p0.driverClass=com.mysql.cj.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/money?serverTimezone=GMT%2B8&characterEncoding=UTF-8
c3p0.user=root
c3p0.password=123456
c3p0.acquireIncrement=3
c3p0.initialPoolSize=10
c3p0.maxIdleTime=60
c3p0.maxPoolSize=150
c3p0.minPoolSize=5
工具类(封装)
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DemoC3p0 {
//获取数据源(自动读取src下面的配置文件,并完成初始化)
private static ComboPooledDataSource ds = new ComboPooledDataSource();
//提供获取方法
public static Connection getConne() {
Connection conn = null;
try {
conn = ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//提供归还方法
public static void returnConne(Connection conn , PreparedStatement prst , ResultSet rs) {
try {
if(rs != null)
rs.close();
if(prst != null)
prst.close();
if(conn != null)
//c3p0实现类中close是归还而非关闭
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
测试
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
public class TestC3p0 {
@Test
public void testC3p0() {
Connection conn = DemoC3p0.getConne();
PreparedStatement prst = null;
ResultSet rs = null;
String sql = "select * from countmoney";
try {
//预执行
prst = conn.prepareStatement(sql);
rs = prst.executeQuery();
while(rs.next()) {
System.out.println(rs.getString("mname") + "\t" + rs.getDouble("mmoney"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
DemoC3p0.returnConne(conn, prst, rs);
}
}
}
控制台
九月 30, 2020 2:48:59 下午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
九月 30, 2020 2:48:59 下午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.2.1 [built 20-March-2013 10:47:27 +0000; debug? true; trace: 10]
九月 30, 2020 2:48:59 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ …… ]
张三 1000.0
李四 1000.0
【DBUTils工具0】
一、DBUTils简介
DBUtils工具简介
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。尤其结合连接池使用效果更为理想。
Commons DbUtils的核心是两个类一个接口:
1、DBUtils类:主要为关闭连接,装载JDBC驱动程序之类的常规工作提供方法,都是静态的方法。
2、QueryRunner类:为我们提供两个重要方法,调用方法之前需要先创建一个QueryRunner的对象。
QueryRunner qRunner=new QueryRunner(new ComboPooledDataSource());
创建对象时需要传入一个连接池的数据源,这里结合c3p0连接池来完成。
qRunner.update():支持DML操作
qRunner.query():支持DQL操作
两个方法都有多个重载,可以根据需求选择不同的重载方法。
3、ResultSetHandler接口:用于处理ResultSet结果集,将结果集的的数据转换成不同形式。该接口的实现类有很多:
二、JavaBean
实体类,一般放在entity中。属性与表中的字段一一对应
表
JavaBean
package com.demo.entity;
public class Money {
//一定要私有化,不然会被随意拿取(把门)
private int midd;
private String mname;
private double mmoney;
public Money() {
super();
}
public Money(int midd, String mname, double mmoney) {
super();
this.midd = midd;
this.mname = mname;
this.mmoney = mmoney;
}
public int getMidd() {
return midd;
}
public void setMidd(int midd) {
this.midd = midd;
}
public String getMname() {
return mname;
}
public void setMname(String mname) {
this.mname = mname;
}
public double getMmoney() {
return mmoney;
}
public void setMmoney(double mmoney) {
this.mmoney = mmoney;
}
}
测试代码
使用JavaBean存储数据
package com.demo.day09;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.demo.entity.Money;
public class TestJavaBean {
//将查询结果封装在list集合中(持久性)
public List<Money> setBean(){
List<Money> list = new ArrayList<Money>();
//DemoC3p0.getConne();见上文C3P0连接池0
Connection conn = DemoC3p0.getConne();
PreparedStatement prst = null;
ResultSet rs = null;
String sql = "select * from countmoney";
try {
prst = conn.prepareStatement(sql);
rs = prst.executeQuery();
while(rs.next()) {
Money mo = new Money();
mo.setMidd(rs.getInt("midd"));
mo.setMname(rs.getString("mname"));
mo.setMmoney(rs.getDouble("mmoney"));
list.add(mo);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DemoC3p0.returnConne(conn, prst, rs);
}
return list;
}
//调用封装结果的方法,得到有结果的list集合
@Test
public void javabean() {
List<Money> list = setBean();
for (Money money : list) {
System.out.println(money.getMname());
}
}
}
控制台
九月 30, 2020 3:27:58 下午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
九月 30, 2020 3:27:59 下午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.2.1 [built 20-March-2013 10:47:27 +0000; debug? true; trace: 10]
九月 30, 2020 3:27:59 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement …… -> …… false ]
张三
李四
三、DBUTils之DML操作0
增加
//增加一条
public int insertOne(Money mo) {
//放入c3p0数据源
ComboPooledDataSource ds = new ComboPooledDataSource();
QueryRunner qr = new QueryRunner(ds);
int rows = 0;
String sql = "insert into countmoney(mname,mmoney) values(?,?)";
try {
rows = qr.update(sql,mo.getMname(),mo.getMmoney());
} catch (SQLException e) {
e.printStackTrace();
}
return rows;
}
//c测试
@Test
public void insert() {
Money mo = new Money(0,"HBW",123456);
int i = insertOne(mo);
System.out.println(i);
}
删除
//删除
public int deleteOne(Money mo) {
//放入c3p0数据源
ComboPooledDataSource ds = new ComboPooledDataSource();
QueryRunner qr = new QueryRunner(ds);
int rows = 0;
String sql = "delete from countmoney where mname =?";
try {
rows = qr.update(sql,mo.getMname());
} catch (SQLException e) {
e.printStackTrace();
}
return rows;
}
//c测试
@Test
public void delete() {
Money mo = new Money(0,"HBW",123456);
int i = deleteOne(mo);
System.out.println(i);
}
修改
注意:修改两个值时,使用逗号隔开,而不是and
//修改
public int updateOne(Money mo) {
//放入c3p0数据源
ComboPooledDataSource ds = new ComboPooledDataSource();
QueryRunner qr = new QueryRunner(ds);
int rows = 0;
//String sql = "update countmoney set mname =? and mmoney =? where midd =?";
String sql = "update countmoney set mname =? , mmoney =? where midd =?";
try {
rows = qr.update(sql,mo.getMname(),mo.getMmoney(),mo.getMidd());
} catch (SQLException e) {
e.printStackTrace();
}
return rows;
}
//c测试
@Test
public void update() {
Money mo = new Money(6,"HBW",124);
int i = updateOne(mo);
System.out.println(i);
}
四、DBUTils之DQL操作0
查全部
使用ResultSetHandler接口的实现类:BeanListHandler
package com.demo.day09;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test;
import com.demo.entity.Money;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DemoDBUTils {
public List<Money> selectAll(){
List<Money> list = new ArrayList<Money>();
//放入c3p0数据源
ComboPooledDataSource ds = new ComboPooledDataSource();
QueryRunner qr = new QueryRunner(ds);
//查询语句
String sql = "select * from countmoney";
try {
//将查询到的每一条记录封装到JavaBean中,再将这些JavaBean封装到List集合中,赋值给list
//Money.class:反射的方式(要求:JavaBean中成员对象必须与数据库中的表的字段同名)
list = qr.query(sql, new BeanListHandler<>(Money.class));
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
//测试封装结果
@Test
public void cesi() {
List<Money> list = selectAll();
for (Money m : list) {
System.out.println(m.getMname());
}
}
}
查一条数据
使用ResultSetHandler接口的实现类:BeanHandler
//查一条
public Money selectOne(){
Money mo = new Money();
//查询语句
String sql = "select * from countmoney where midd=?";
try {
//将查询到的每一条记录封装到JavaBean中,再将这些JavaBean封装到List集合中,赋值给list
//Money.class:反射的方式(要求:JavaBean中成员对象必须与数据库中的表的字段同名)
mo = qr.query(sql, new BeanHandler<>(Money.class),1);
} catch (SQLException e) {
e.printStackTrace();
}
return mo;
}
//测试
@Test
public void testSelectOne() {
Money mo = selectOne();
System.out.println(mo.getMidd() + "\t" + mo.getMname());
}
查单列数据
使用ResultSetHandler接口的实现类:ScalarHandler
//查单列数据
public String selectCount(){
String mname = null;
//查询语句
String sql = "select mname from countmoney where midd=?";
try {
//将查询到的每一条记录封装到JavaBean中,再将这些JavaBean封装到List集合中,赋值给list
//Money.class:反射的方式(要求:JavaBean中成员对象必须与数据库中的表的字段同名)
Object obj = qr.query(sql, new ScalarHandler(),1);
mname = String.valueOf(obj);
} catch (SQLException e) {
e.printStackTrace();
}
return mname;
}
//测试
@Test
public void testSelectCount() {
System.out.println(selectCount());
}
注意:
Object类型转int类型时,需要先转为long,再转为int
练习——使用DUBTils工具类和C3P0数据源完成对数据库的操作
PS
目录带0,表示有需要主义的点
【】表示重要
删除线表示已经解决
仅学习记录使用,若存在错误,还望指正
Java代码操作数据库(上)👉点击👈