JDBC-03 数据库事务&DAO

NiuMT 2020-06-03 20:58:30
Java

第6章: 数据库事务

6.1 数据库事务介绍

6.2 JDBC事务处理

【案例:用户AA向用户BB转账100】

public void testJDBCTransaction() {
    Connection conn = null;
    try {
        // 1.获取数据库连接
        conn = JDBCUtils.getConnection();
        // 2.开启事务
        conn.setAutoCommit(false);
        // 3.进行数据库操作
        String sql1 = "update user_table set balance = balance - 100 where user = ?";
        update(conn, sql1, "AA");

        // 模拟网络异常
        //System.out.println(10 / 0);

        String sql2 = "update user_table set balance = balance + 100 where user = ?";
        update(conn, sql2, "BB");
        // 4.若没有异常,则提交事务
        conn.commit();
    } catch (Exception e) {
        e.printStackTrace();
        // 5.若有异常,则回滚事务
        try {
            conn.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
    } finally {
        try {
            //6.恢复每次DML操作的自动提交功能
            conn.setAutoCommit(true);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        //7.关闭连接
        JDBCUtils.closeResource(conn, null, null); 
    }  
}

其中,对数据库操作的方法为:

//使用事务以后的通用的增删改操作(version 2.0)
public void update(Connection conn ,String sql, Object... args) {
    PreparedStatement ps = null;
    try {
        // 1.获取PreparedStatement的实例 (或:预编译sql语句)
        ps = conn.prepareStatement(sql);
        // 2.填充占位符
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
        // 3.执行sql语句
        ps.execute();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 4.关闭资源
        JDBCUtils.closeResource(null, ps);
    }
}

6.3 事务的ACID属性

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

6.3.1 数据库的并发问题

6.3.2 四种隔离级别

6.3.3 在MySql中设置隔离级别

// *****************************************************
    @Test
    public void testTransactionSelect() throws Exception{
        Connection conn = JDBCUtils.getConnection();
        //获取当前连接的隔离级别
        System.out.println(conn.getTransactionIsolation());
        //设置数据库的隔离级别:
        conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        //取消自动提交数据
        conn.setAutoCommit(false);

        String sql = "select user,password,balance from user_table where user = ?";
        User user = getInstance(conn, User.class, sql, "CC");
        System.out.println(user);
    }

    @Test
    public void testTransactionUpdate() throws Exception{
        Connection conn = JDBCUtils.getConnection();
        //取消自动提交数据
        conn.setAutoCommit(false);
        String sql = "update user_table set balance = ? where user = ?";
        update(conn, sql, 5000,"CC");
        Thread.sleep(15000);
        System.out.println("修改结束");
    }
    //通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
    public <T> T getInstance(Connection conn,Class<T> clazz,String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            // 获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            // 通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                T t = clazz.newInstance();
                // 处理结果集一行数据中的每一个列
                for (int i = 0; i < columnCount; i++) {
                    // 获取列值
                    Object columValue = rs.getObject(i + 1);
                    // 获取每个列的列名
                    // String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    // 给t对象指定的columnName属性,赋值为columValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, ps, rs);
        }
        return null;
    }

第7章:DAO及相关实现类

1566726681515

1566745811244

【BaseDAO.java】

package com.atguigu.bookstore.dao;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;


/**
 * 定义一个用来被继承的对数据库进行基本操作的Dao
 * @author HanYanBing
 * @param <T>
 */
public abstract class BaseDao<T> {
    private QueryRunner queryRunner = new QueryRunner();
    // 定义一个变量来接收泛型的类型
    private Class<T> type;

    // 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
    public BaseDao() {
        // 获取子类的类型
        Class clazz = this.getClass();
        // 获取父类的类型
        // getGenericSuperclass()用来获取当前类的父类的类型
        // ParameterizedType表示的是带泛型的类型
        ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
        // 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型
        // 这个方法会返回一个Type的数组
        Type[] types = parameterizedType.getActualTypeArguments();
        // 获取具体的泛型的类型·
        this.type = (Class<T>) types[0];
    }

    /**
     * 通用的增删改操作
     * 
     * @param sql
     * @param params
     * @return
     */
    public int update(Connection conn,String sql, Object... params) {
        int count = 0;
        try {
            count = queryRunner.update(conn, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        } 
        return count;
    }

    /**
     * 获取一个对象
     * 
     * @param sql
     * @param params
     * @return
     */
    public T getBean(Connection conn,String sql, Object... params) {
        T t = null;
        try {
            t = queryRunner.query(conn, sql, new BeanHandler<T>(type), params);
        } catch (SQLException e) {
            e.printStackTrace();
        } 
        return t;
    }

    /**
     * 获取所有对象
     * 
     * @param sql
     * @param params
     * @return
     */
    public List<T> getBeanList(Connection conn,String sql, Object... params) {
        List<T> list = null;
        try {
            list = queryRunner.query(conn, sql, new BeanListHandler<T>(type), params);
        } catch (SQLException e) {
            e.printStackTrace();
        } 
        return list;
    }

    /**
     * 获取一个但一值得方法,专门用来执行像 select count(*)...这样的sql语句
     * 
     * @param sql
     * @param params
     * @return
     */
    public Object getValue(Connection conn,String sql, Object... params) {
        Object count = null;
        try {
            // 调用queryRunner的query方法获取一个单一的值
            count = queryRunner.query(conn, sql, new ScalarHandler<>(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        } 
        return count;
    }
}

【BookDAO.java】

package com.atguigu.bookstore.dao;

import java.sql.Connection;
import java.util.List;

import com.atguigu.bookstore.beans.Book;
import com.atguigu.bookstore.beans.Page;

public interface BookDao {

    /**
     * 从数据库中查询出所有的记录
     * 
     * @return
     */
    List<Book> getBooks(Connection conn);

    /**
     * 向数据库中插入一条记录
     * 
     * @param book
     */
    void saveBook(Connection conn,Book book);

    /**
     * 从数据库中根据图书的id删除一条记录
     * 
     * @param bookId
     */
    void deleteBookById(Connection conn,String bookId);

    /**
     * 根据图书的id从数据库中查询出一条记录
     * 
     * @param bookId
     * @return
     */
    Book getBookById(Connection conn,String bookId);

    /**
     * 根据图书的id从数据库中更新一条记录
     * 
     * @param book
     */
    void updateBook(Connection conn,Book book);

    /**
     * 获取带分页的图书信息
     * 
     * @param page:是只包含了用户输入的pageNo属性的page对象
     * @return 返回的Page对象是包含了所有属性的Page对象
     */
    Page<Book> getPageBooks(Connection conn,Page<Book> page);

    /**
     * 获取带分页和价格范围的图书信息
     * 
     * @param page:是只包含了用户输入的pageNo属性的page对象
     * @return 返回的Page对象是包含了所有属性的Page对象
     */
    Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice);

}

【UserDAO.java】

package com.atguigu.bookstore.dao;

import java.sql.Connection;

import com.atguigu.bookstore.beans.User;

public interface UserDao {

    /**
     * 根据User对象中的用户名和密码从数据库中获取一条记录
     * 
     * @param user
     * @return User 数据库中有记录 null 数据库中无此记录
     */
    User getUser(Connection conn,User user);

    /**
     * 根据User对象中的用户名从数据库中获取一条记录
     * 
     * @param user
     * @return true 数据库中有记录 false 数据库中无此记录
     */
    boolean checkUsername(Connection conn,User user);

    /**
     * 向数据库中插入User对象
     * 
     * @param user
     */
    void saveUser(Connection conn,User user);
}

【BookDaoImpl.java】

package com.atguigu.bookstore.dao.impl;

import java.sql.Connection;
import java.util.List;

import com.atguigu.bookstore.beans.Book;
import com.atguigu.bookstore.beans.Page;
import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.BookDao;

public class BookDaoImpl extends BaseDao<Book> implements BookDao {

    @Override
    public List<Book> getBooks(Connection conn) {
        // 调用BaseDao中得到一个List的方法
        List<Book> beanList = null;
        // 写sql语句
        String sql = "select id,title,author,price,sales,stock,img_path imgPath from books";
        beanList = getBeanList(conn,sql);
        return beanList;
    }

    @Override
    public void saveBook(Connection conn,Book book) {
        // 写sql语句
        String sql = "insert into books(title,author,price,sales,stock,img_path) values(?,?,?,?,?,?)";
        // 调用BaseDao中通用的增删改的方法
        update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath());
    }

    @Override
    public void deleteBookById(Connection conn,String bookId) {
        // 写sql语句
        String sql = "DELETE FROM books WHERE id = ?";
        // 调用BaseDao中通用增删改的方法
        update(conn,sql, bookId);

    }

    @Override
    public Book getBookById(Connection conn,String bookId) {
        // 调用BaseDao中获取一个对象的方法
        Book book = null;
        // 写sql语句
        String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?";
        book = getBean(conn,sql, bookId);
        return book;
    }

    @Override
    public void updateBook(Connection conn,Book book) {
        // 写sql语句
        String sql = "update books set title = ? , author = ? , price = ? , sales = ? , stock = ? where id = ?";
        // 调用BaseDao中通用的增删改的方法
        update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId());
    }

    @Override
    public Page<Book> getPageBooks(Connection conn,Page<Book> page) {
        // 获取数据库中图书的总记录数
        String sql = "select count(*) from books";
        // 调用BaseDao中获取一个单一值的方法
        long totalRecord = (long) getValue(conn,sql);
        // 将总记录数设置都page对象中
        page.setTotalRecord((int) totalRecord);

        // 获取当前页中的记录存放的List
        String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books limit ?,?";
        // 调用BaseDao中获取一个集合的方法
        List<Book> beanList = getBeanList(conn,sql2, (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
        // 将这个List设置到page对象中
        page.setList(beanList);
        return page;
    }

    @Override
    public Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice) {
        // 获取数据库中图书的总记录数
        String sql = "select count(*) from books where price between ? and ?";
        // 调用BaseDao中获取一个单一值的方法
        long totalRecord = (long) getValue(conn,sql,minPrice,maxPrice);
        // 将总记录数设置都page对象中
        page.setTotalRecord((int) totalRecord);

        // 获取当前页中的记录存放的List
        String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books where price between ? and ? limit ?,?";
        // 调用BaseDao中获取一个集合的方法
        List<Book> beanList = getBeanList(conn,sql2, minPrice , maxPrice , (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
        // 将这个List设置到page对象中
        page.setList(beanList);

        return page;
    }

}

【UserDaoImpl.java】

package com.atguigu.bookstore.dao.impl;

import java.sql.Connection;

import com.atguigu.bookstore.beans.User;
import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.UserDao;

public class UserDaoImpl extends BaseDao<User> implements UserDao {

    @Override
    public User getUser(Connection conn,User user) {
        // 调用BaseDao中获取一个对象的方法
        User bean = null;
        // 写sql语句
        String sql = "select id,username,password,email from users where username = ? and password = ?";
        bean = getBean(conn,sql, user.getUsername(), user.getPassword());
        return bean;
    }

    @Override
    public boolean checkUsername(Connection conn,User user) {
        // 调用BaseDao中获取一个对象的方法
        User bean = null;
        // 写sql语句
        String sql = "select id,username,password,email from users where username = ?";
        bean = getBean(conn,sql, user.getUsername());
        return bean != null;
    }

    @Override
    public void saveUser(Connection conn,User user) {
        //写sql语句
        String sql = "insert into users(username,password,email) values(?,?,?)";
        //调用BaseDao中通用的增删改的方法
        update(conn,sql, user.getUsername(),user.getPassword(),user.getEmail());
    }

}

【Book.java】

package com.atguigu.bookstore.beans;
/**
 * 图书类
 * @author songhongkang
 *
 */
public class Book {

    private Integer id;
    private String title; // 书名
    private String author; // 作者
    private double price; // 价格
    private Integer sales; // 销量
    private Integer stock; // 库存
    private String imgPath = "static/img/default.jpg"; // 封面图片的路径
    //构造器,get(),set(),toString()方法略
}

【Page.java】

package com.atguigu.bookstore.beans;

import java.util.List;
/**
 * 页码类
 * @author songhongkang
 *
 */
public class Page<T> {

    private List<T> list; // 每页查到的记录存放的集合
    public static final int PAGE_SIZE = 4; // 每页显示的记录数
    private int pageNo; // 当前页
//    private int totalPageNo; // 总页数,通过计算得到
    private int totalRecord; // 总记录数,通过查询数据库得到

【User.java】

package com.atguigu.bookstore.beans;
/**
 * 用户类
 * @author songhongkang
 *
 */
public class User {

    private Integer id;
    private String username;
    private String password;
    private String email;