事物 和 数据库连接池 和 DBUtils


前言------内容多一点,周末有了点事情,好在今天终于总结完了,加油!

事物

什么是事物

Transaction 其实是一组操作,里面包含了很多的单一的逻辑。只要有一个逻辑没有执行成功,那么这个事物就是失败的,这个时候所有的数据回滚到最初始的状态(回滚)

事物有什么作用

处理一组逻辑,如果其中一个逻辑不成功就是失败。
例子:银行转账,当A转账给B,突然断电,转账失败,数据回滚最初的状态

事物怎么用

1、事物不是针对的数据库,而是只针对数据库连接的对象(connection),如果再开一个对象,默认是自动提交的
2、事物会自动提交的

使用命令行方式演示事物

  • 开启事务
    start transaction
  • 提交或者回滚事物
    commit:提交事物
    rollback:回滚事物
  1. 关闭自动提交的功能

命令:show variables like ‘%commit%’
查询含有commit字符串的所有变量名称(variables变量的意思)
命令:set autocommit = off
关闭自动提交事物的功能(auto自动commit提交,off关闭)
在这里插入图片描述

  1. 演示事物

只有当commit才提交数据,数据库表中的数据才会改变,如果不提交,表中数据是不会变的。
原来表中数据zhangsan:1000 lishi:1100,zhangsan减去100,张三的数据在命令行中查的话是改变了,但是数据库中没变,可以使用数据库工具查看。只有commit提交之后才会改变

在这里插入图片描述

使用代码的方式演示事物

代码里面的事物,主要针对连接来的(Connection con)
事物,只会针对于 增 删 改 操作,查询不会修改数据,用不到事物

  1. 通过con.setAutoCommit(false); 来关闭自动提交事物的设置
  2. 通过con.commit(); 提交事物
  3. 通过con.rollback(); 回滚事物
@Test
    public void textTransaction(){
        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.closeResource(conn,ps,rs);
        }
    }

事物的特性ACID【面试】

  • 原子性

指的是,事物中包含的逻辑,不可分割

  • 一致性

指的是,事物执行的前后,数据的一致性

  • 隔离性

指的是,事物在执行期间,不受其他事物的影响

  • 持久性

指的是,事物执行完毕之后,数据应该持久的保存在磁盘上

事物的安全问题和隔离级别【面试】

安全问题

不考虑事物的隔离级别的设置,那么会出现 读问题,和 写问题

List item

读问题

  1. 脏读:指的是,一个事物读取到另一个事物还未提交的数据
  2. 不可重复读:一个事物读到了另一个事物提交的数据,导致两次查询前后数据不一致
  3. 幻读:一个事物读到了另一个事物已经提交的插入的数据,导致多次查询的结果不一致

写问题(丢失更新)

: 丢失更新
在这里插入图片描述

丢失更新解决办法

  • 悲观锁

可以在查询的时候,加入 for update

在这里插入图片描述

  • 乐观锁

要求程序员自己控制。

在这里插入图片描述

隔离级别

  • 读未提交(Read Uncommitted)

引发“脏读”
指的是,一个事物读到另一个事物还未提交的数据,引发“脏读”,读取到的是数据库内存上的数据,而并非是真正磁盘上的数据

例子:

  1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。 设置当前窗口的事务隔离> 级别为 读未提交 命令如下:

      //查询当前事物的隔离级别
     select @@tx_isolation;   
     //设置当前窗口 事物 隔离级别为 read uncommitted
     set session transaction isolation level read uncommitted;
    
  2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交

  3. 在A窗口重新执行查询, 会看到B窗口没有提交的数据。

  • 读已提交(Read committed )

解决脏读,引发不可重复读
与前面的读未提交刚好相反,这个隔离级别是 ,只能读取到其他事务已经提交的数据,那些没有提交的数据是读不出来的。
但是这会造成一个问题是:前后读取到的结果不一样。 发生了不可重复!!!, 所谓的不可重复读,就是不能执行多次读取,否则出现结果不一 。

例子1:

  1. 开启一个命令行窗口A, 设置A窗口的隔离级别为 读已提交,开始事务,然后查询表中记录。 设置当前窗口的事务隔离级别为 读已提交 命令如下:

       set session transaction isolation level read committed;
    
  2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交

  3. 在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。

例子2:

  1. 设置A窗口的隔离级别为 读已提交
  2. A B 两个窗口都开启事务, 在B窗口执行更新操作。
  3. 在A窗口执行的查询结果不一致。 一次是在B窗口提交事务之前,一次是在B窗口提交事务之后。
  • 重复读(Repeatable Read)

解决脏读,不可重复读。 不能解决幻读
Repeatable Read 【重复读】 - MySql 默认的隔离级别就是这个。
该隔离级别, 可以让事务在自己的会话中重复读取数据,并且不会出现结果不一样的状况,即使其他事务已经提交了,也依然还是显示以前的数据。
事物与事物之间是分开的,互不影响。

例子:

  1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。 设置当前窗口的事务隔离级别为 重复读 命令如下:

      set session transaction isolation level repeatable read;
    
  2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交

  3. 在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。

  4. 在B窗口执行提交。

  5. 在A窗口中执行查看, 这时候查询结果,和以前的查询结果一致。不会发生改变。

  • 可串行化(Serializable)

解决 脏读、不可重复读、幻读
如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利, 谁后打开事务,谁就只能等着,等前面的那个事务,提交或者回滚后,才能执行。 但是这种隔离级别一般比较少用。 容易造成性能上的问题。 效率比较低。

Serializable 可以防止上面的所有问题,但是都使用该隔离级别也会有些问题。 比如造成并发的性能问题。 其他的事务必须得等当前正在操作表的事务先提交,才能接着往下,否则只能一直在等着。

分类 Value
按效率划分,从高到低 读未提交 > 读已提交 > 可重复读 > 可串行化
按拦截度划分,从高到低 可串行化 > 可重复读 > 读已提交 > 读未提交

数据库连接池

什么是连接池

连接池就是里面存有多个连接数据库的连接对象,然后统一管理

  1. 数据库的连接对象创建工作,比较消耗性能。
  2. 一开始现在内存中开辟一块空间(集合) , 一开先往池子里面放置 多个连接对象。 后面需要连接的话,直接从池子里面去。不要去自己创建连接了。 使用完毕, 要记得归还连接。确保连接对象能循环利用。

在这里插入图片描述

连接池的作用

  1. 资源重复利用,避免了重复创建连接对象。
    连接对象使用完之后,再归还到池子中统一管理
  2. 更快的响应速度
    连接池里面在一开始就创建了好多个连接对象,使用的话直接拿就好了,无需再创建

提高效率,因为

自定义连接池

  • 代码实现
  1. MyDataSource类
public class MyDataSource  implements DataSource {
    static List<Connection> list = new ArrayList<Connection>();
    static {
        for (int i = 0;i < 10; i++){
            Connection conn = JDBCUtil.getConn();
            list.add(conn);
        }
    }
    /**
     * 通过这个方法获得连接对象
     * return Connection
     * */
    @Override
    public Connection getConnection() throws SQLException {
        if(list.size() == 0){
            //在增加3个连接对象
            for (int i = 0; i < 3;i++){
                Connection conn = JDBCUtil.getConn();
                list.add(conn);
            }
        }
        return list.remove(0);
    }

    /**
     * 用完之后在归还连接对象,调用这个方法
     * param conn
     * */
    public void addBack(Connection conn){
        list.add(conn);
    }
  1. DoMain类。,运行测试类
public class doMain {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        //这种写法不能够面向接口编程,多了一个addBack()方法,DataSource dataSource = new MyDataSource();
        MyDataSource myDataSource = new MyDataSource();
        try {
            conn = myDataSource.getConnection();
            String sql = "insert into  account values (null ,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"wangwu");
            ps.setInt(2,2530);
            ps.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            myDataSource.addBack(conn);
            JDBCUtil.closeResource1(conn,ps);

        }
    }

}
  • 出现的问题:
  1. 需要记住额外的addBack()方法,不能面向接口编程
  2. 有的时候还得建立多个连接池,(new MyDataSource()),浪费资源
    单例模式可以解决
  3. 无法面向接口编程
  • 解决的办法

    以addBack() 方法入手
    1. 直接修改源代码     行不通
    2. 使用继承,重写close方法,但是不知道DataSource接口的实现类,没法继承
    3. 使用装饰者模式 
    

装饰者模式

  • 分析问题

由于我们自定义的数据库连接池,引发了一个问题,多了一个用完连接归还的addBack()方法,导致不能用面向接口编程。
所以,我们打算用重写Connection类中的close()方法,之前的close()方法是关闭资源,我们重写之后使他不关闭资源,而是把用完的Connection连接对象添加到List的集合中

  • 装饰者模式的代码实现
    MyDataSource类
public class MyDataSource  implements DataSource {
    static List<Connection> list = new ArrayList<Connection>();
    static  ConnectionWriter connectionWriter = null;
    static {
        for (int i = 0;i < 10; i++){
            Connection conn = JDBCUtil.getConn();
            list.add(conn);
        }
    }
    /**
     * 通过这个方法获得连接对象
     * return Connection
     * */
    @Override
    public Connection getConnection() throws SQLException {
        if(list.size() == 0){
            //在增加3个连接对象
            for (int i = 0; i < 3;i++){
                Connection conn = JDBCUtil.getConn();
                list.add(conn);
            }
        }
        //在抛出去之前包装一下
        Connection conn = list.remove(0);
        ConnectionWriter c = new ConnectionWriter(conn,list);
        return c;
    }

ConnectionWriter类

public class ConnectionWriter implements Connection {

    Connection conn = null;
    List<Connection> list = null;
    public  ConnectionWriter(Connection conn, List<Connection> list){
        super();
        this.conn = conn;
        this.list = list;
    }

    @Override
    public void close() throws SQLException {
        System.out.println("没有归还之前的connection对象有几个:"+list.size());
        list.add(conn);
        System.out.println("归还之后的connection对象有几个:"+list.size());
    }


    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

DoMain测试类

public class doMain {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        DataSource dataSource = new MyDataSource();
        try {
            conn = dataSource.getConnection();
            String sql = "insert into  account values (null ,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"wangwu1");
            ps.setInt(2,2530);
            ps.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
//            dataSource.addBack(conn);
            JDBCUtil.closeResource1(conn,ps);
        }
    }
}

开源连接池

自己写的连接池有些问题,然后,有些人就写了一些连接池,其中开源的常用的是c3p0,和 dbcp

c3p0【重点】

什么是c3p0

c3p0是一个开源的连接池,他实现了jdbc和数据源的绑定。主要负责建立和管理数据库连接,支持JDBC3的规范和JDBC2的标准扩展。目前使用他的框架有
Spring 和 Hibernate

c3p0的使用

 ComboPooledDataSource类
  • 使用配置文件
    配置文件c3p0-config.xml 名字必须是这一个
<c3p0-config>
<!-- 默认的数据库连接池-->
  <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="JdbcUrl">jdbc:mysql://localhost:3306/stus</property>
    <property name="user">root</property>
    <property name="password">123456</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>

    <user-overrides user="test-user">
      <property name="maxPoolSize">10</property>
      <property name="minPoolSize">1</property>
      <property name="maxStatements">0</property>
    </user-overrides>

  </default-config>

  <!-- This app is massive! 
	这一部分是当使用别的数据库时候,根据<named-config name="oracle"> 中的name判断使用哪个数据库连接池
	-->
  <named-config name="oracle"> 
    <property name="acquireIncrement">50</property>
    <property name="initialPoolSize">100</property>
    <property name="minPoolSize">50</property>
    <property name="maxPoolSize">1000</property>

    <!-- intergalactoApp adopts a different approach to configuring statement caching -->
    <property name="maxStatements">0</property> 
    <property name="maxStatementsPerConnection">5</property>

    <!-- he's important, but there's only one of him -->
    <user-overrides user="master-of-the-universe"> 
      <property name="acquireIncrement">1</property>
      <property name="initialPoolSize">1</property>
      <property name="minPoolSize">1</property>
      <property name="maxPoolSize">5</property>
      <property name="maxStatementsPerConnection">50</property>
    </user-overrides>
  </named-config>
</c3p0-config>

代码实现:

先导入jar包,c3p0-0.9.1.2.jar

/**
配置文件名必须使用 c3p0-config.xml ,当类加载的时候直接解析读取该文,如果改名就读取不到了,除非自己写源代码
*/
//创建数据库连接池
ComboPooledDatasource dataSource = new ComboPooledDataSource();
//获取连接
Conection conn = dataSource.getConnection();
//。。。后面和以前连接数据库一样
  • 不使用配置文件
    代码实现:
comborPooledDataSource dataSource=  new comboPooledDataSource();
dataSource.setDriverClass("com.mysql:jdbc:Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/stus);
dataSource.setUser("root");
dataSource.setPassword("123456");

Connection con = dataSource.getConnection();
//.......

dbcp

什么是dbcp

dbcp也是一种开源的连接池,是java数据库连接池的一种,由Apach开发,主要作用于让程序自己管理连接池的释放和断开

dbcp的使用

  • 使用配置文件
    自己定义一个dbcpconfig.properties文件,放在src目录下

导入jar包
commons-dbcp-1.4.jar 和 commons-pool-1.5.6.jar
使用DataSource接口的实现类 BasicDataSource类 和 BasicDataSourceFictory类
createDataSource() 方法

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/stus
username=root
password=123456

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

代码实现:

@Test
    public void Text(){
        try {
            BasicDataSourceFactory factory = new BasicDataSourceFactory();
            Properties properties = new Properties();
            InputStream is = Dbcp.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            properties.load(is);
            DataSource dataSource = factory.createDataSource(properties);

            QueryRunner queryRunner = new QueryRunner(dataSource);
           User b = queryRunner.query("select * from account where id = ?", new ResultSetHandler<User>() {
                @Override
                public User handle(ResultSet resultSet) throws SQLException {
                    User a = new User();
                    while (resultSet.next()){
                        a.setName(resultSet.getString("name"));
                        a.setMoney(resultSet.getInt("money"));
                    }
                    return a;
                }
            },3);
            System.out.println(b.toString());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
  • 不使用配置文件
    代码实现
//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");

DBUtils数据库的工具类

什么是DBUtils

DBUtils是一个开源的工具类,由Apach开发,对数据库的CRUD操作进行了一些简单的封装,不影响开发性能,简化了CRUD的操作

DbUtils的使用

导入jar包 commons-dbutils-1.4.jar

QueryRunner 类   new QueryRunner(DataSource接口的实现类);
类中的 query() 和 update() 方法

代码实现:

ComboPooledDataSource dataSource = new ComnoPooledDataSource();
QueryRunner queryRunner = new QueryRanner(dataSource);

//执行 增 删 改操作
queryRunner.update("sql语句",参数);
int update = queryRunner.update("update account set money = ? where id = ?", 12000, 3);

//执行查操作
queryRunner.query("sql语句",结果集对象接口,参数);

//1、通过匿名内部类去实现ResultSetHandler接口,
User user = queryRunner.query("select * from account where id = ?", new ResultSetHandler<User> (){
	@Override
    public User handle(ResultSet resultSet) throws SQLException {
        User user1 = new User();
         while (resultSet.next()){
			user1.setName(resultSet.getString("name"));
            user1.setMoney(resultSet.getInt("money"));
         }

         return user1;
    }
},3);
System.out.println(user.toString());


//2、ResultSetHandler是一个接口,直接去new框架中写好的实现类
//查询一个用户,用户有多个属性,用一个对象去接收他 ,定义的时候注意变量一定要与数据库的列名一样才能传递过来数据
// 为什么要传入一个字节码对象?通过反射获取一个实例
User query = queryRunner.query("select * from account where id = ?", new 						  BeanHandler<User>(User.class),3);
System.out.println(query.toString());


//获取多个用户
List<User> list = queryRunner.query("select *from account", new 	BeanListHandler<User>(User.class));
	Iterator<User> iterator = list.iterator();
	while (iterator.hasNext()){
		User u = iterator.next();
		System.out.println(u.toString());
 	}

ResultSetHandler接口的常用的实现类

  • 最常用的两个实现类(封装成对象)

    BeanHandler: 查询单个数据,封装成一个对象,。 在对象中的属性应该和  数据库中列名一致(对象中的属性对应的是表中列的值),
    否则不会数据封装不过来
    
    BeanListHandler:查询多个数据,然后每个数据封装成一个对象,然后,多个对象存在一个集合中
    
  • 封装成数组

ArrayHandler:查询单个数据,然后封装成数组
ArrayListHandler:查询多个数据,然后每个数据封装成一个数组(数组中的元素是表中每一列的值),然后多个数组存到List集合

  • 封装成Map集合

Maphandler:查询单个数据,然后封装成Map集合
MapListHandler:查询多个数据,封装成一个集合,集合里面的元素是map。

不常用的实现类

ColumnListHandler
KeyedHandler
ScalarHandler 通常处理一些sql语句中的 count() sum() min() max() avg() ,返回的是 Long数据类型,只能用Long类型去接受,不能用int

DbUtils内部源码解析

 源数据:解释数据的数据
 获取参数的数据库相关的源数据
 ParameterMetaData metaData = ps.getParameterMetaData();
 获取占位符有几个
 int count = metaData.getParameterCount();

 泛型的使用

元数据

Meata data

描述数据的数据 String sql , 描述这份sql字符串的数据叫做元数据

数据库元数据 DatabaseMetaData
参数元数据 ParameterMetaData
结果集元数据 ResultSetMetaData

  • 增、删、改 、的update()方法
public class CRUDUtils {
    @Test
    public void updateText(){
//        update("insert into account values (null,?,?)","mingming",153);
        update("delete from account where id =?",7);
    }

    //实现增,删,改的功能
    public void update(String sql,Object ... param){

        try {
            Connection conn = JDBCUtil.getConn();

            PreparedStatement ps = conn.prepareStatement(sql);

            //源数据:定义数据的数据,
            ParameterMetaData metaData = ps.getParameterMetaData();
            //获取原数据 中占位符的个数,也就是 ? 的个数
            int parameterCount = metaData.getParameterCount();
            for (int i = 0; i<parameterCount;i++){
                ps.setObject(i+1,param[i]);
            }
            ps.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 查询 query() 方法
  1. CRUDUtils类
public class CRUDUtils {
	 @Test
    public void queryText(){
        User user = query("select * from account where id = ?", new ResultSetHandler<User>() {

            @Override
            public User hand(ResultSet resultSet) throws SQLException {
                User user = new User();
                while (resultSet.next()) {
                    user.setName(resultSet.getString("name"));
                    user.setMoney(resultSet.getInt("money"));
                }
                return user;
            }
        }, 3);
    }

    //实现查的功能
    public <T> T query(String sql,ResultSetHandler<T> resultSetHandler,Object ... param){

        try {
            Connection conn = JDBCUtil.getConn();

            PreparedStatement ps = conn.prepareStatement(sql);
            //获取参数的数据库相关的源数据
            ParameterMetaData metaData = ps.getParameterMetaData();
            //获取占位符有几个
            int count = metaData.getParameterCount();
            for (int i = 0; i<count;i++){
                ps.setObject(i+1,param[i]);
            }


            //这个地方主要是返回一个resultSet,要给方法的执行者封装,并返回一个封装对象
            ResultSet resultSet = ps.executeQuery();
            //将封装的使用权交给函数的调用者
            T t = (T)resultSetHandler.hand(resultSet);
            return t;

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.自定义的 ResultSetHandler接口

public interface ResultSetHandler<T> {
    //查询结果集,返回封装对象
    T hand(ResultSet resultSet) throws SQLException;
}

猜你喜欢

转载自blog.csdn.net/weixin_44142032/article/details/88751922