数据库连接池:
连接对象的缓冲区。负责申请,分配管理,释放连接的操作
不用数据库连接池很费事:
因为每一次Java程序要与mysql服务器通信(执行sql),都需要先建立连接,然后才能通信。
建立连接池的好处:
A:事先准备好一些连接的话,用户来了就分配给它,不用现建立连接,更快。
B:连接用完,放回池中,可以重复利用,连接的利用率增加。
C:池可以限制上限,这样服务器就不会轻易崩溃,更安全。
用了连接池,我们面对的是连接池,close()放回到连接池,不再是关闭连接
数据库连接池技术
- DBCP 数据库连接池
- C3P0 数据库连接池
- DRUID 阿里【德鲁伊连接池,用的最多】集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池
数据库连接池:javaX.sql.DataSource接口,具体的实现是开源组织。开源万岁!
创建德鲁伊连接池方式1:不使用properties文件
@Test
//创建连接池方式一:
public void test1() throws SQLException {
// 数据库连接池:javaX.sql.DataSource:接口,具体的实现是开源组织
DruidDataSource dds = new DruidDataSource();
dds.setDriverClassName("com.mysql.cj.jdbc.Driver");
dds.setUrl("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC");
dds.setUsername("root");
dds.setPassword("123456");
// 设置初始容量,和最大活动数
dds.setInitialSize(5);
dds.setMaxActive(10);
// 从连接池获取连接
Connection conn = dds.getConnection();
System.out.println(conn);
// close()放回到连接池,不是关闭与远程数据库连接
conn.close();
}
创建德鲁伊连接池方式2:使用properties文件,druid.properties文件是在src下.
public class ConnPoolTest {
@Test
//创建连接池方式二:
public void test2() throws Exception {
Properties props = new Properties();
//通过类加载器帮我们加载资源配置文件
props.load(ConnPoolTest.class.getClassLoader().getResourceAsStream("druid.properties"));
// 调用静态方法直接用druid.properties文件创建德鲁伊连接池
// 工厂模式,通过工厂类的静态方法,创建连接池对象。
DataSource ds = DruidDataSourceFactory.createDataSource(props);
Connection conn = ds.getConnection();
System.out.println(conn);
conn.close();
}
}
MyQueryRunner
public class MyQueryRunner {
public int update(Connection conn, String sql, Object ... args){
PreparedStatement ps = null;
int row = 0;
try {
//创建ps,为后面发送sql语句做准备
ps = conn.prepareStatement(sql);
//取可变参数中的数据,填充到占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1, args[i]);
}
//执行增删改sql,返回受影响的行数
row = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//这里是不能关闭conn的,因为在一个事务中会有多个增删改,即多次调用
//这个update方法,不能在第一次调用后就关闭这个Connection连接
JDBCUtils.close(null, ps);
}
return row;
}
}
事务:身为程序员,必须对事务非常清楚
什么是事务:一个commit和一个rollback之间【commit】的一个或多个DML
默认情况:当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,
如果执行成功,就会向数据库自动提交,而不能回滚
转账举例:
A账户减去100 balance
B账户加100
//转账过程中出现一个问题 int i =10/0;
转账前:
转账过程:
因为俩个不同的连接,没法成为同一个事务,所以要变化一下上一节我们写的通用增删改
MyQueryRunner mqr = new MyQueryRunner();
@Test
public void test2() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql1 = "update user_table set balance = balance - 100 where user = ?";
mqr.update(conn, sql1, "AA");
//模拟故障
int i = 10 / 0;
String sql2 = "update user_table set balance = balance + 100 where user = ?";
//在俩个不同的连接上是没法成为同一个事务的,所以
mqr.update(conn, sql2, "BB");
//事务提交
JDBCUtils.close(conn, null, null);
}
解决思路:这俩个DML操作要么都成功,要么都失败。让俩个dml成为一个事务
1、取消自动提交事务 Connection连接对象.setAutoCommit(false)
2、成功使用commit,失败在异常处理里使用roolback
MyQueryRunner mqr = new MyQueryRunner();
@Test
public void test1() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);//取消自动提交(开启事务)
String sql1 = "update user_table set balance = balance - 100 where user = ?";
mqr.update(conn, sql1, "AA");
//模拟故障
int i = 10 / 0;
String sql2 = "update user_table set balance = balance + 100 where user = ?";
//在俩个不同的连接上是没法成为同一个事务的,所以
mqr.update(conn, sql2, "BB");
//事务提交
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
try {
conn.rollback();//事务回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, null, null);
}
}
有了事务,要是一条不成功,则会回滚。
事务的ACID属性
1.原子性 要么都成功,要么都失败。
2.一致性 保证总金额2000
3.隔离性 isolation 并发执行的各个事务之间不能互相干扰
4.持久性,一旦commit对数据库中数据的改变就是永久性的,就算数据库出现故障不应该对其有任何影响
事务引发的并发3个问题:
- 脏读 : t1读取了t2修改但是没有提交的数据,t2一旦回滚,这个数据对t1属于临时无效的数据
- 不可重复度 :针对读了字段值。在t1事务不知情 的情况下t2事务 改了数据。
- 幻读:针对增加或删除了表的行数,在t1事务不知情 的情况下t2事务改表
数据库的隔离级别:
解决多个事务访问共享数据的问题.
一个事务与其他事务隔离的程度称为隔离级别。隔离级别越高, 数据一致性就越好, 但并发性越弱.
Mysql 支持 4 种事务隔离级别.
Mysql 默认的事务隔离级别为: REPEATABLE READ(可重复读)
全局变量 @@tx_isolation, 表示当前的事务隔离级别.
-- 查看当前的隔离级别: SELECT @@tx_isolation -- 设置数据库系统的全局的隔离级别: set global transaction isolation level read committed -- 设置当前 mySQL 连接的隔离级别: set transaction isolation level read committed;
DButils
是 Apache 组织提供的一个开源 JDBC工具类库,将常用的操作数据库的JDBC的类和方法集合在一起,它是对JDBC的简单封装。
ResultSetHandler,此接口用于处理数据库查询操作得到的结果集。不同的结果集的情形,由其不同的子类来实现,如下:
BeanHandler:把结果集转为一个 Bean
BeanListHandler:把结果集转为一个 Bean 的集合
MapHandler:把结果集转为一个 Map
MapListHandler:把结果集转为一个 Map 的 List
ScalarHandler:把结果集转为一个类型的数据返回
QueryRunner,提供数据库操作的一系列重载的update()和query()操作
- 可以实现增、删、改、查、批处理、
- 考虑了事务处理需要共用Connection。
- 该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
- 不需要手动关闭连接,runner会自动关闭连接,释放到连接池中
public class DButilsTest {
private static DataSource ds = null;
static {
Properties props = new Properties();
try {
//加载文件获取文件中的连接数据库四要素
props.load(DButilsTest.class.getClassLoader().getResourceAsStream("druid.properties"));
//创建连接池,并初始化(可以在properties中指定)
ds = DruidDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
}
//使用开源组织dbutils中的QueryRunner
//提供数据库操作的一系列重载的update()和query()操作
private QueryRunner qr = new QueryRunner(ds);
@Test
public void test1() throws SQLException {
String sql = "insert into customers(id, name, email, birth) values(?,?,?,?)";
//使用开源组织提供的DBUtils,进行通用的增删改,代替我们自己写的工具MyQueryRunner
int row = qr.update(sql, 21, "孙尚香", "ssx@abc.com", "2002-8-19");
System.out.println("已影响" + row + "行");
}
}
public class DButilsTest {
private static DataSource ds = null;
static {
Properties props = new Properties();
try {
//加载文件获取文件中的连接数据库四要素
props.load(DButilsTest.class.getClassLoader().getResourceAsStream("druid.properties"));
//创建连接池,并初始化(可以在properties中指定)
ds = DruidDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
}
//使用开源组织dbutils中的QueryRunner
//提供数据库操作的一系列重载的update()和query()操作
private QueryRunner qr = new QueryRunner(ds);
@Test
public void test5() throws SQLException {
// String sql = "select count(*) from customers";
String sql = "select max(birth) from customers";
// ScalarHandler:把结果集转为一个类型的数据返回
Object obj = qr.query(sql, new ScalarHandler());
System.out.println(obj);
}
@Test
public void test4() throws SQLException {
String sql = "select id, name, email, birth from customers where id <= ?";
// MapListHandler:把结果集转为一个 Map 的 List
List
写一个通用的DAO
把数据库中的记录<---->Java的对象对应起来。
DAO就是为了完成增删改查,接口是可以扩展的。
public interface DAO{//Database Access Object public Object getValue(Connection conn, String sql, Object ... args) throws SQLException; public List getList(Connection conn, String sql, Object ... args) throws SQLException; public T get(Connection conn, String sql, Object ... args) throws SQLException; public int update(Connection conn, String sql, Object ... args) throws SQLException; }
提供实现类:抽象类【转恶霸用来】
BaseDAO
考虑事务 在new 的时候qr就不需要new的时候放连接了
public abstract class BaseDAOimplements DAO { private QueryRunner qr = new QueryRunner(); private Class cl; public BaseDAO(){ //要是没有反射工具类,就用上面注释掉的代码 cl = ReflectionUtils.getSuperGenericType(this.getClass()); } @Override public Object getValue(Connection conn, String sql, Object... args) throws SQLException { return qr.query(conn, sql, new ScalarHandler(), args); } @Override public List getList(Connection conn, String sql, Object... args) throws SQLException { return qr.query(conn, sql, new BeanListHandler<>(cl), args); } @Override public T get(Connection conn, String sql, Object... args) throws SQLException { return qr.query(conn, sql, new BeanHandler<>(cl), args); } @Override public int update(Connection conn, String sql, Object... args) throws SQLException { return qr.update(conn, sql, args); } }
创建一个DAO继承BaseDao
public class CustomerDAO extends BaseDAO{ }
@Test
public void test1(){
CustomerDAO cd = new CustomerDAO();
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select id, name, email, birth from customers where id = ?";
Customer customer = cd.get(conn, sql, 20);
System.out.println(customer);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, null, null);
}
}



