三层架构、JDBC事务、DBUtils

今日内容

  1. 1、三层架构
  2. 2、三层架构结合事务
  3. 3ThreadLocal解决事务问题
  4. 4DAO通用封装方法
  5. 5DbUtils的使用

第一节 三层架构

1.1 引言

三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的即为了“高内聚低耦合”的思想。

1.2各层作用

数据访问层(DAL)的作用:
从数据源加载数据(select)
向数据源写入数据(Insert/Update)
从数据源删除数据(Delete)
业务逻辑层(BLL)的作用:
从DAL中获取数据,以供UI显示用
从UI中获取用户指令和数据,执行业务逻辑
从UI获取用户指令和数据,通过DAL写入数据源

显示层(UI)的作用:
向用户展现特定业务数据
采集用户的输入信息和操作

1.3引用关系

UI——->BLL———>DAL

1.4 分层优缺点

优点

1、开发人员可以只关注整个结构中的其中某一层;
2、可以很容易的用新的实现来替换原有层次的实现;
3、可以降低层与层之间的依赖;
4、有利于标准化;
5、利于各层逻辑的复用。
6、结构更加的明确
7、在后期维护的时候,极大地降低了维护成本和维护时间

缺点
1、降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
2、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。
3、增加了开发成本。

1.5 业务层(service)

什么是业务?

  1. 代表用户完成的一个业务功能,可以由一个或多个DAO的调用组成。(软件所提供的一个功能都叫业务)

转账:一次转账。两个DAO操作:扣钱,加钱

1.5.1定义业务层

包名:service

实现类包名:impl

接口名:xxxService

实现类:xxxServiceImpl

业务层Service接口代码:

  1. package com.qf.www.service;
  2. /*
  3. service层是业务层,是个接口
  4. */
  5. public interface EmpService {
  6. //1查询
  7. List<Emp> findAll();
  8. //2更新
  9. void update(Emp e);
  10. //3删除
  11. void delete(int empno);
  12. //4添加
  13. void add(Emp e);
  14. }

业务层service实现类代码

  1. package com.qf.www.service.impl;
  2. import com.qf.www.service.EmpService;
  3. /*
  4. 业务层实现类主要是创建DAO层的数据访问对象,然后调用方法完成业务功能
  5. */
  6. public class EmpServiceImpl implements EmpService {
  7. private EmpDao empDao = new EmpDaoImpl();
  8. @Override
  9. public List<Emp> findAll() {
  10. List<Emp> list = empDao.findAll();
  11. return list;
  12. }
  13. @Override
  14. public void update(Emp e) {
  15. empDao.update(e);
  16. }
  17. @Override
  18. public void delete(int empno) {
  19. empDao.delete(empno);
  20. }
  21. @Override
  22. public void add(Emp e) {
  23. empDao.add(e);
  24. }
  25. }

第二节 三层结合事务

通过三层结合事务,完成转账练习

2.1项目准备

  1. 创建Java项目
  2. 导入需要的jar mysql驱动、 druid.jar
  3. 添加数据库配置文件
  • db.properties
  1. #连接设置
  2. driverClassName=com.mysql.jdbc.Driver
  3. url=jdbc:mysql://localhost:3306/account
  4. username=root
  5. password=1234
  6. #<!-- 初始化连接 -->
  7. initialSize=10
  8. #最大连接数量
  9. maxActive=50
  10. #<!-- 最小空闲连接 -->
  11. minIdle=5
  12. #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
  13. maxWait=5000

2.2编写Java代码

应用三层架构,其中dao层,我们已经搭建过,只需要添加service业务层和view视图层

2.2.1分包

  1. com.qf.dao
  1. com.qf.dao.impl
  2. com.qf.service
  3. com.qf.service.impl
  4. com.qf.view
  5. com.qf.utils
  6. com.qf.domain

2.2.2编写DataSourceUtils工具类

DatasourceUtils工具类,优化获取连接,优化事务操作

  1. package com.itqf.utils;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.SQLException;
  5. import java.sql.Statement;
  6. import javax.sql.DataSource;
  7. import com.qf.utils.DruidUtils;
  8. public class DataSourceUtils {
  9. private static DruidDataSource ds=null;
  10. //静态代码块
  11. static {
  12. InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("db.properties");
  13. Properties properties=new Properties();
  14. try {
  15. properties.load(is);
  16. dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. /**
  22. * 获取数据源
  23. * @return 连接池
  24. */
  25. public static DataSource getDataSource(){
  26. return ds;
  27. }
  28. /**
  29. * 从当前线程上获取连接
  30. * @return 连接
  31. * @throws SQLException
  32. */
  33. public static Connection getConnection() throws SQLException{
  34. //--从
  35. try {
  36. Connection connection = ds.getConnection();
  37. return connection;
  38. } catch (SQLException e) {
  39. e.printStackTrace();
  40. }
  41. return null;
  42. }
  43. //关闭
  44. public static void close(){
  45. Connection connection = getConnection();
  46. try {
  47. connection.close();
  48. } catch (SQLException e) {
  49. e.printStackTrace();
  50. }
  51. }
  52. /**
  53. *---- 开启事务
  54. * @throws SQLException
  55. */
  56. public static void startTransaction() throws SQLException{
  57. //获取连接//开启事务
  58. getConnection().setAutoCommit(false);
  59. }
  60. /**
  61. *--- 事务提交
  62. */
  63. public static void commitAndClose(){
  64. try {
  65. //获取连接
  66. Connection conn = getConnection();
  67. //提交事务
  68. conn.commit();
  69. //释放资源
  70. conn.close();
  71. } catch (SQLException e) {
  72. e.printStackTrace();
  73. }
  74. }
  75. /**
  76. * ----事务回滚
  77. */
  78. public static void rollbackAndClose(){
  79. try {
  80. //获取连接
  81. Connection conn = getConnection();
  82. //事务回滚
  83. conn.rollback();
  84. //释放资源
  85. conn.close();
  86. } catch (SQLException e) {
  87. e.printStackTrace();
  88. }
  89. }
  90. }

2.2.3编写DAO层代码

dao层进行具体数据库操作

实体类:

  1. package com.qf.www.entity;
  2. public class Account {
  3. private int id;
  4. private String name;
  5. private double money;
  6. @Override
  7. public String toString() {
  8. return "Account{" +
  9. "id=" + id +
  10. ", name='" + name + '\'' +
  11. ", money=" + money +
  12. '}';
  13. }
  14. public int getId() {
  15. return id;
  16. }
  17. public void setId(int id) {
  18. this.id = id;
  19. }
  20. public String getName() {
  21. return name;
  22. }
  23. public void setName(String name) {
  24. this.name = name;
  25. }
  26. public double getMoney() {
  27. return money;
  28. }
  29. public void setMoney(double money) {
  30. this.money = money;
  31. }
  32. public Account() {
  33. }
  34. public Account(int id, String name, double money) {
  35. this.id = id;
  36. this.name = name;
  37. this.money = money;
  38. }
  39. }
  1. package com.qf.www.dao.impl;
  2. import com.qf.www.dao.AccountDao;
  3. import com.qf.www.utils.DBUtils;
  4. import org.apache.commons.dbutils.QueryRunner;
  5. import java.sql.Connection;
  6. import java.sql.PreparedStatement;
  7. import java.sql.ResultSet;
  8. import java.sql.SQLException;
  9. public class AccountDaoImpl implements AccountDao {
  10. private Connection connection = null;
  11. private PreparedStatement preparedStatement = null;
  12. private ResultSet resultSet = null;
  13. @Override
  14. public void updateSave(int cardNo, double money) {
  15. connection = DBUtils.getConnection();
  16. String sql = "update account set money = money+? where id = ?";
  17. try {
  18. preparedStatement = connection.prepareStatement(sql);
  19. preparedStatement.setDouble(1, money);
  20. preparedStatement.setInt(2, cardNo);
  21. preparedStatement.executeUpdate();
  22. } catch (SQLException e) {
  23. e.printStackTrace();
  24. }finally {
  25. DBUtils.close();
  26. }
  27. }
  28. @Override
  29. public void updateTake(int cardNo, double money) {
  30. connection = DBUtils.getConnection();
  31. String sql = "update account set money = money-? where id = ?";
  32. try {
  33. preparedStatement = connection.prepareStatement(sql);
  34. preparedStatement.setDouble(1, money);
  35. preparedStatement.setInt(2, cardNo);
  36. preparedStatement.executeUpdate();
  37. } catch (SQLException e) {
  38. e.printStackTrace();
  39. }finally {
  40. DBUtils.close();
  41. }
  42. }
  43. }

2.2.4编写业务层代码

  1. package com.itqf.service;
  2. import java.sql.Connection;
  3. import java.sql.SQLException;
  4. import java.sql.Savepoint;
  5. import com.itqf.dao.AccountDao;
  6. import com.itqf.dao.AccountDaoDButis;
  7. import com.itqf.dao.AccountDaoLocal;
  8. import com.itqf.utils.DataSourceUtils;
  9. import com.itqf.utils.JdbcUtils;
  10. /**
  11. * jdbc+threadlocal
  12. *
  13. * @author Administrator
  14. *
  15. */
  16. public class AccountServiceImpl {
  17. /**
  18. * 转账业务逻辑
  19. * @param from
  20. * @param to
  21. * @param money
  22. * @throws Exception
  23. */
  24. public void transfer(int from, int to,double money) throws Exception {
  25. AccountDaoDButis accountDao = new AccountDaoDButis();
  26. try {
  27. //开启事务
  28. DataSourceUtils.startTransaction();
  29. //1.转出
  30. accountDao.out(from,money);
  31. //2.转入
  32. accountDao.in(to,money);
  33. DataSourceUtils.commitAndClose();
  34. } catch (Exception e) {
  35. // TODO Auto-generated catch block
  36. e.printStackTrace
  37. DataSourceUtils.rollbackAndClose();
  38. throw e; //接着向外抛
  39. }
  40. }
  41. }

2.2.4编写视图层代码

视图层主要用于用户输入转账内容,调用业务层

  1. package com.qf.www.view;
  2. import com.qf.www.service.AccountService;
  3. import com.qf.www.service.impl.AccountServiceImpl;
  4. import java.util.Scanner;
  5. public class StartView {
  6. public static void main(String[] args) {
  7. Scanner scanner = new Scanner(System.in);
  8. System.out.println("欢迎登陆转账系统:");
  9. System.out.println("请输入卡号:");
  10. int from = scanner.nextInt();
  11. System.out.println("请输入对方卡号");
  12. int to = scanner.nextInt();
  13. System.out.println("请输入转账金额");
  14. double money = scanner.nextDouble();
  15. //调用业务层
  16. AccountService accountService = new AccountServiceImpl();
  17. //调用转账方法,传入参数
  18. accountService.transfer(from, to, money);
  19. }
  20. }

第三节 ThreadLocal

三层结合事务现存在的问题:

当前事务出现异常,一个DAO操作执行成功,一个DAO失败,则需要进行回滚,但是却发现回滚失败了,原因是在整个事务操作过程中,每一个环节应用的数据库连接对象都不是同一个,所以事务的控制从头到尾不是一个连接。

需要使用ThreadLocal, 可以在整个线程(单条执行路径)所持有的Map中,存储一个键(threadlocal)值(conn)。

3.1优化DataSourceUtils

  1. package com.itqf.utils;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.SQLException;
  5. import java.sql.Statement;
  6. import javax.sql.DataSource;
  7. import com.qf.utils.DruidUtils;
  8. public class DataSourceUtils {
  9. private static DruidDataSource ds=null;
  10. //创建
  11. private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
  12. //静态代码块
  13. static {
  14. InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("db.properties");
  15. Properties properties=new Properties();
  16. try {
  17. properties.load(is);
  18. dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. /**
  24. * 获取数据源
  25. * @return 连接池
  26. */
  27. public static DataSource getDataSource(){
  28. return ds;
  29. }
  30. /**
  31. * 从当前线程上获取连接
  32. * @return 连接
  33. * @throws SQLException
  34. */
  35. public static Connection getConnection() throws SQLException{
  36. try {
  37. //--从线程获取链接
  38. Connection connection = threadLocal.get();
  39. if(connection==null){
  40. //第一次获取 创建一个连接 和当前的线程绑定
  41. connection = ds.getConnection();
  42. //----绑定
  43. threadLocal.set(connection);
  44. }
  45. return connection;
  46. } catch (SQLException e) {
  47. e.printStackTrace();
  48. }
  49. return null;
  50. }
  51. //关闭
  52. public static void close(){
  53. Connection connection = getConnection();
  54. try {
  55. //和当前线程解绑
  56. threadLocal.remove();
  57. connection.close();
  58. } catch (SQLException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. /**
  63. *---- 开启事务
  64. * @throws SQLException
  65. */
  66. public static void startTransaction() throws SQLException{
  67. //获取连接//开启事务
  68. getConnection().setAutoCommit(false);
  69. }
  70. /**
  71. *--- 事务提交
  72. */
  73. public static void commitAndClose(){
  74. try {
  75. //获取连接
  76. Connection conn = getConnection();
  77. //提交事务
  78. conn.commit();
  79. //释放资源
  80. conn.close();
  81. } catch (SQLException e) {
  82. e.printStackTrace();
  83. }
  84. }
  85. /**
  86. * ----事务回滚
  87. */
  88. public static void rollbackAndClose(){
  89. try {
  90. //获取连接
  91. Connection conn = getConnection();
  92. //事务回滚
  93. conn.rollback();
  94. //释放资源
  95. conn.close();
  96. } catch (SQLException e) {
  97. e.printStackTrace();
  98. }
  99. }
  100. }

3.2优化DAO层代码

  1. package com.qf.www.dao.impl;
  2. import com.qf.www.dao.AccountDao;
  3. import com.qf.www.utils.DBUtils;
  4. import org.apache.commons.dbutils.QueryRunner;
  5. import java.sql.Connection;
  6. import java.sql.PreparedStatement;
  7. import java.sql.ResultSet;
  8. import java.sql.SQLException;
  9. public class AccountDaoImpl implements AccountDao {
  10. private Connection connection = null;
  11. private PreparedStatement preparedStatement = null;
  12. private ResultSet resultSet = null;
  13. @Override
  14. public void updateSave(int cardNo, double money) {
  15. connection = DBUtils.getConnection();
  16. String sql = "update account set money = money+? where id = ?";
  17. try {
  18. preparedStatement = connection.prepareStatement(sql);
  19. preparedStatement.setDouble(1, money);
  20. preparedStatement.setInt(2, cardNo);
  21. preparedStatement.executeUpdate();
  22. } catch (SQLException e) {
  23. e.printStackTrace();
  24. }finally {
  25. // DBUtils.close();不在dao调用关闭连接的方法!
  26. }
  27. }
  28. @Override
  29. public void updateTake(int cardNo, double money) {
  30. connection = DBUtils.getConnection();
  31. String sql = "update account set money = money-? where id = ?";
  32. try {
  33. preparedStatement = connection.prepareStatement(sql);
  34. preparedStatement.setDouble(1, money);
  35. preparedStatement.setInt(2, cardNo);
  36. preparedStatement.executeUpdate();
  37. } catch (SQLException e) {
  38. e.printStackTrace();
  39. }finally {
  40. // DBUtils.close();不在dao调用关闭连接的方法!
  41. }
  42. }
  43. }

第四节 DAO通用方法

4.1增删改通用方法:

  1. //封装 增、删、改的通用方法
  2. public static int executeUpdate(String sql, Object... params) {
  3. PreparedStatement preparedStatement = null;
  4. Connection connection = getConnection();
  5. try {
  6. preparedStatement = connection.prepareStatement(sql);
  7. for (int i = 0; i < params.length; i++) {
  8. preparedStatement.setObject(i + 1, params[i]);
  9. }
  10. return preparedStatement.executeUpdate();
  11. } catch (SQLException e) {
  12. e.printStackTrace();
  13. } finally {
  14. closeAll(null, preparedStatement, connection);
  15. }
  16. return 0;
  17. }

4.2查询通用方法:

定义封装规则接口:

  1. package com.qf.www.utils;
  2. import java.sql.ResultSet;
  3. //封装规则。封装对象为泛型,通用。参数需要的是ResultSet对象
  4. public interface RowMapper<T> {
  5. T getRow(ResultSet rs);
  6. }

定义封装方式:

  1. package com.qf.www.utils;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. //定义具体的封装方式,决定通用查询封装的是具体某个对象
  5. //遍历ResultSet,取值做对象属性的赋值
  6. public class AccountRowMapper implements RowMapper<Account> {
  7. @Override
  8. public Account getRow(ResultSet rs) {
  9. Account account = new Account();
  10. try {
  11. account.setId(rs.getInt(1));
  12. account.setMoney(rs.getDouble(2));
  13. } catch (SQLException e) {
  14. e.printStackTrace();
  15. }
  16. return account;
  17. }
  18. }

查询通用方法:

  1. //查询通用方法 泛型方法。返回值为泛型集合,因为查询可能是单个或多个
  2. //参数:sql语句、封装方式、参数列表
  3. public static <T> List<T> commonsSelect(String sql , RowMapper<T> mapper,Object... params){
  4. PreparedStatement preparedStatement = null;
  5. Connection connection = getConnection();
  6. ResultSet rs = null;
  7. //定义泛型集合,类型由封装方式的具体对象来决定
  8. List<T> list=new ArrayList<T>();
  9. try {
  10. preparedStatement = connection.prepareStatement(sql);
  11. //循环为占位符赋值
  12. for(int i= 0;i<params.length;i++){
  13. preparedStatement.setObject(i+1, params[i]);
  14. }
  15. rs = preparedStatement.executeQuery();
  16. //迭代
  17. while(rs.next()){
  18. //获得一行数据,调用封装方式,得到对象,添加到集合中
  19. list.add(mapper.getRow(rs));
  20. }
  21. return list;
  22. } catch (SQLException e) {
  23. e.printStackTrace();
  24. }finally {
  25. closeAll(rs, preparedStatement, connection);
  26. }
  27. return null;
  28. }

第五节 DBUtils使用

Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。

5.1 DBUtils简介

DBUtils是java编程中的数据库操作实用工具,小巧简单实用,

1.对于数据表的读操作,可以把结果转换成List,Array,Set等java集合,便于程序员操作。

2.对于数据表的写操作,也变得很简单(只需写sql语句)。

DBUtils包括主要类

DbUtils类:启动类

ResultSetHandler接口:转换类型接口

  1. --ArrayHandler类:实现类,把记录转化成数组
  2. --ArrayListHandler类:把记录转化成数组,并放入集合中
  3. --ColumnListHandler类:取某一列的数据。封装到List中。
  4. --**ScalarHandle**r类:适合获取一行一列数据。
  5. --**BeanHandler**类:实现类,把记录转成对象。
  6. --**BeanListHandler**类:实现类,把记录转化成List,使记录为JavaBean类型的对象

QueryRunner类:执行SQL语句的类

5.2 DBUtils工具类封装

5.2.1 项目准备

  • 创建项目
  • 导入jar包 工具类 配置文件
    commons-dbutils-1.6.jar
    druid-1.1.5.jar
    数据库驱动包
    DruidUtils.java工具类
    database.properties配置文件

5.2.2 实现代码

  1. public class ResultHanlder {
  2. @Test
  3. public void testArrayHander() throws SQLException {
  4. // ArrayHandler:适合取1条记录。把该条记录的每列值封装到一个数组中Object[]
  5. QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
  6. Object[] query = runner.query("select * from school where empno = ?", new ArrayHandler(), 1234);
  7. for (Object object : query) {
  8. System.out.println(object);
  9. }
  10. }
  11. @Test
  12. public void testArrayListHander() throws SQLException {
  13. // ArrayHandler:适合取1条记录。把该条记录的每列值封装到一个数组中Object[]
  14. QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
  15. List<Object[]> query = runner.query("select * from emp ", new ArrayListHandler());
  16. for (Object[] objects : query) {
  17. for (Object object : objects) {
  18. System.out.println(object);
  19. }
  20. }
  21. }
  22. @Test
  23. public void testColumnListHander() throws SQLException {
  24. // ColumnListHandler:取某一列的数据。封装到List中。
  25. QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
  26. List<Object> query = runner.query("select * from emp ", new ColumnListHandler<Object>(2));
  27. for (Object objects : query) {
  28. System.out.println(objects);
  29. }
  30. }
  31. @Test
  32. public void testScalarHandler() throws SQLException {
  33. // ScalarHandler:适合取单行单列数据
  34. QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
  35. Object query = runner.query("select count(*) from emp ", new ScalarHandler());
  36. System.out.println(query);
  37. }
  38. @Test
  39. public void testBeanHandler() throws SQLException {
  40. // BeanHandler:适合取单行单列数据
  41. QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
  42. Employee query = runner.query("select * from emp where empno=1234 ", new BeanHandler<Employee>(Employee.class));
  43. System.out.println(query.toString());
  44. }
  45. @Test
  46. public void testBeanListHandler() throws SQLException {
  47. // BeanHandler:适合取多行多列数据
  48. QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
  49. List<Employee> query2 = runner.query("select * from emp", new BeanListHandler<Employee>(Employee.class));
  50. for (Employee employee : query2) {
  51. System.out.println(employee);
  52. }
  53. }
  54. }

总结

1、三层架构 DAO、Service、View Utils

2、结合事务的三层架构,在DBUtils工具类里,封装对事务的一系列操作

3、ThreadLocal解决多个DAO操作,connection对象不同步的问题。

4、DAO通用方法:executeUpdate增删改、commonsSelect查询方法(重点)。

5、DBUtils的使用,QueryRunner