2.5三层架构和DBUtils
三层架构和Apache的DbUtils
回顾
- 封装数据库工具类
四个基本功能 (1)加载驱动 (2) 获取连接 (3) 释放连接 (4) 执行命令 (String sql,Object…params) - 封装查询数据
把数据库中数据封装成实体对象 - DAO模式
把数据库操作封装起来,把业务逻辑代码和数据访问代码隔离开。职责分离,降低耦合性
四个部分 1 DAO接口 2 DAO实现类 3 实体类 4 数据库工具类 - 连接池
Druid连接池的使用
今日内容
- 三层架构
- JDBC控制事务
- 三层实现事务
- ThreadLocal类使用
- Apache的DbUtils工具类使用
第一节 三层架构
-
DAO模式的缺点
DAO模式仅仅提供了对数据访问的操作,业务逻辑代码和负责展示的代码是耦合在一起的,所以需要把业务代码和负责展示的代码分离开就出现了三层架构。
1.1 什么是三层架构
-
三层架构(3-tier architecture) 就是将整个业务应用划分为:界面(表示)层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data Access Layer)。区分层次的目的即为了高内聚低耦合的思想。
-
每层功能:
- 数据访问层:是对数据库的基本操作(增删改查等),具体为业务逻辑层提供数据服务。
- 业务逻辑层:主要是针对具体的问题的操作,对数据业务逻辑处理,比如注册、转账等。
- 界面(表示)层:主要界面方式有WEB或Window窗口、控制台等,负责向用户展现特定业务数据、采集用户的输入信息和操作。
1.2 引用关系
- 表示层调用业务逻辑层,业务逻辑层调用数据访问层。下层为上层提供服务。上层调用下层。
1.3 分层优缺点
-
优点:
- 有助于分层开发
- 无损替换
- 降低层与层之间的依赖
- 提高代码的重用性
-
缺点:
- 增加开发成本
- 降低系统性能
1.4 业务层(service)
-
什么是业务?
代表用户完成的一个业务功能,可以由一个或多个DAO的调用组成。(软件所提供的一个功能都叫业务)
转账:一次转账。两个DAO操作:扣钱,加钱 -
在DAO模式基础上添加service层,service层的包命名规则
- 接口包名:service
- 接口名:xxxService
- 实现类包名:impl
- 实现类:xxxServiceImpl
-
三层架构演示
- 业务层Service接口代码:
/*
service层是业务层,是个接口,业务中方法名可以和DAO中方法相同,也可以不相同
*/
public interface EmpService {
//1查询
List<Emp> findAll();
//2更新
void update(Emp e);
//3删除
void delete(int empno);
//4添加
void add(Emp e);
}
- 业务层service实现类代码
/*
业务层实现类主要是创建DAO层的数据访问对象,然后调用方法完成业务功能
*/
public class EmpServiceImpl implements EmpService {
private EmpDao empDao = new EmpDaoImpl();
@Override
public List<Emp> findAll() {
List<Emp> list = empDao.findAll();
return list;
}
@Override
public void update(Emp e) {
empDao.update(e);
}
@Override
public void delete(int empno) {
empDao.delete(empno);
}
@Override
public void add(Emp e) {
empDao.add(e);
}
}
第二节 JDBC控制事务
回顾:
-
事务是由完成任务的一个或多个操作组成,这些操作作为一个整体不能分割,要么全部执行成功,要么全部失败。
-
事务四个特性:
- 原子性(Atomicity,或称不可分割性)
- 一致性(Consistency)
- 隔离性(Isolation,又称独立性)
- 持久性(Durability)
-
MySQL中与事务控制有关的SQL语句:
-
BEGIN
或START TRANSACTION;
开启事务 -
COMMIT;
提交事务 -
ROLLBACK;
回滚事务
-
-
在JDBC中实现事务控制需要使用Connection对象提供的方法
-
conn.setAutoCommit(false);
开启事务 -
conn.commit();
提交事务 -
conn.rollback();
回滚事务
-
-
案例实现:使用事务实现转账
(1)准备表
CREATE TABLE account(
`id` INT PRIMARY KEY,
`name` VARCHAR(20) NOT NULL,
`money` DOUBLE(10,2)
)
(2)代码实现
public static void main(String[] args) {
Connection connection=null;
PreparedStatement pstat1=null;
PreparedStatement pstat2=null;
//1注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//2获取连接
connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
//3创建命令
//3.1开启事务 ,设置事务自动提交为false
connection.setAutoCommit(false);
pstat1=connection.prepareStatement("update account set money=money-1000 where name='张莎强'");
pstat1.executeUpdate();
//int c=10/0;
pstat2=connection.prepareStatement("update account set money=money+1000 where name='小苍'");
pstat2.executeUpdate();
System.out.println("转账成功...");
//3.2提交事务
connection.commit();
} catch (Exception e) {
System.out.println("出现异常");
try {
connection.rollback();//出现问题,要回滚(撤销事务做过的修改)
connection.commit();//可加也不不加
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}finally {
try {
if(pstat1!=null)
pstat1.close();
if(pstat2!=null)
pstat2.close();
if(connection!=null)
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
第三节 三层实现事务
3.1项目准备
创建Java项目
导入需要的jar包 mysql驱动、 druid.jar
添加数据库配置文件
- db.properties
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/account
username=root
password=1234
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=5000
3.2编写Java代码
- 应用三层架构,其中dao层,我们已经搭建过,只需要添加service业务层和view视图层
3.2.1包结构
com.公司名.dao
com.公司名.dao.impl
com.公司名.service
com.公司名.service.impl
com.公司名.view
com.公司名.utils
com.公司名.domain
3.2.2编写DbUtils工具类
- DBUtils工具类,优化获取连接,优化事务操作
public class DbUtils {
private static DruidDataSource ds=null;
//静态代码块
static {
InputStream is = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties=new Properties();
try {
properties.load(is);
dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据源
* @return 连接池
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 从当前线程上获取连接
* @return 连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
//--从
try {
Connection connection = ds.getConnection();
return connection;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//关闭
public static void close(){
Connection connection = getConnection();
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
*---- 开启事务
* @throws SQLException
*/
public static void startTransaction() throws SQLException{
//获取连接//开启事务
getConnection().setAutoCommit(false);
}
/**
*--- 事务提交
*/
public static void commitAndClose(){
try {
//获取连接
Connection conn = getConnection();
//提交事务
conn.commit();
//释放资源
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* ----事务回滚
*/
public static void rollbackAndClose(){
try {
//获取连接
Connection conn = getConnection();
//事务回滚
conn.rollback();
//释放资源
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.2.3编写DAO层代码
- DAO层进行具体数据库操作
实体类:
public class Account {
private int id;
private String name;
private double money;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public Account() {
}
public Account(int id, String name, double money) {
this.id = id;
this.name = name;
this.money = money;
}
}
public class AccountDaoImpl implements AccountDao {
private Connection connection = null;
private PreparedStatement preparedStatement = null;
private ResultSet resultSet = null;
@Override
public void updateSave(int cardNo, double money) {
connection = DBUtils.getConnection();
String sql = "update account set money = money+? where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, cardNo);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.close();
}
}
@Override
public void updateTake(int cardNo, double money) {
connection = DBUtils.getConnection();
String sql = "update account set money = money-? where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, cardNo);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.close();
}
}
}
3.2.4编写业务层代码
/**
* jdbc+threadlocal
*
* @author Administrator
*
*/
public class AccountServiceImpl {
/**
* 转账业务逻辑
* @param from
* @param to
* @param money
* @throws Exception
*/
public void transfer(int from, int to,double money) throws Exception {
AccountDaoDButis accountDao = new AccountDaoDButis();
try {
//开启事务
DataSourceUtils.startTransaction();
//1.转出
accountDao.out(from,money);
//2.转入
accountDao.in(to,money);
DataSourceUtils.commitAndClose();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace
DataSourceUtils.rollbackAndClose();
throw e; //接着向外抛
}
}
}
3.2.4编写视图层代码
- 视图层主要用于用户输入转账内容,调用业务层
public class StartView {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("欢迎登陆转账系统:");
System.out.println("请输入卡号:");
int from = scanner.nextInt();
System.out.println("请输入对方卡号");
int to = scanner.nextInt();
System.out.println("请输入转账金额");
double money = scanner.nextDouble();
//调用业务层
AccountService accountService = new AccountServiceImpl();
//调用转账方法,传入参数
accountService.transfer(from, to, money);
}
}
第四节 ThreadLocal类
-
三层结合事务现存在的问题:
当前事务出现异常,一个DAO操作执行成功,一个DAO失败,则需要进行回滚,但是却发现回滚失败了,原因是在整个事务操作过程中,每一个环节应用的数据库连接对象都不是同一个,所以事务的控制从头到尾不是一个连接。 -
需要使用
ThreadLocal
, 可以在整个线程(单条执行路径)所持有的Map中,存储一个键值,值是与当前线程关联的Connection对象,只要保证线程是同一个,则获取到的Connection是同一个。
4.1优化DbUtils
public class DbUtils {
private static DruidDataSource ds=null;
//创建
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
//静态代码块
static {
InputStream is = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties=new Properties();
try {
properties.load(is);
dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据源
* @return 连接池
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 从当前线程上获取连接
* @return 连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
try {
//--从线程获取链接
Connection connection = threadLocal.get();
if(connection==null){
//第一次获取 创建一个连接 和当前的线程绑定
connection = ds.getConnection();
//----绑定
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//关闭
public static void close(){
Connection connection = getConnection();
try {
//和当前线程解绑
threadLocal.remove();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
*---- 开启事务
* @throws SQLException
*/
public static void startTransaction() throws SQLException{
//获取连接//开启事务
getConnection().setAutoCommit(false);
}
/**
*--- 事务提交
*/
public static void commitAndClose(){
try {
//获取连接
Connection conn = getConnection();
//提交事务
conn.commit();
//释放资源
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* ----事务回滚
*/
public static void rollbackAndClose(){
try {
//获取连接
Connection conn = getConnection();
//事务回滚
conn.rollback();
//释放资源
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.2优化DAO层代码
public class AccountDaoImpl implements AccountDao {
private Connection connection = null;
private PreparedStatement preparedStatement = null;
private ResultSet resultSet = null;
@Override
public void updateSave(int cardNo, double money) {
connection = DbUtils.getConnection();
String sql = "update account set money = money+? where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, cardNo);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
// DBUtils.close();不在dao调用关闭连接的方法!
}
}
@Override
public void updateTake(int cardNo, double money) {
connection = DbUtils.getConnection();
String sql = "update account set money = money-? where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, cardNo);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
// DBUtils.close();不在dao调用关闭连接的方法!
}
}
}
第五节 Apache的DbUtils使用
-
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
-
下载地址:http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi
5.1 DbUtils简介
-
DbUtils是java编程中的数据库操作实用工具,小巧简单实用。
-
功能:
- 对于数据表的读操作,可以把结果转换成List,Array,Set等java集合,便于程序员操作。
- 对于数据表的写操作,也变得很简单(只需写sql语句)。
-
DbUtils包括主要类
DbUtils类:启动类
ResultSetHandler接口:转换类型接口
–ScalarHandler类:适合获取一行一列数据。
–BeanHandler类:实现类,把记录转成对象。
–BeanListHandler类:实现类,把记录转化成List,使记录为JavaBean类型的对象
--ArrayHandler类:实现类,把记录转化成数组
--ArrayListHandler类:把记录转化成数组,并放入集合中
--ColumnListHandler类:取某一列的数据。封装到List中。
QueryRunner类:执行SQL语句的类
5.2 DbUtils使用步骤
5.2.1 项目准备
- 创建项目
- 导入jar包 、配置文件
MySQL驱动包
commons-dbutils-1.6.jar
druid-1.1.5.jar
druid.properties配置文件
5.2.2 创建数据源工具类
由于Apache的DBUtils工具需要一个数据源对象,所以需要创建工具类返回数据源对象
DruidUtils.java
public class DruidUtils {
private static DruidDataSource dataSource;
static {
try {
Properties properties=new Properties();
InputStream is=DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource;
}
}
5.2.3 实现代码
public class ResultHanlderTest {
@Test
public void testBeanHandler() throws SQLException {
// BeanHandler:适合取单行单列数据
QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
Employee query = runner.query("select * from emp where empno=1234 ", new BeanHandler<Employee>(Employee.class));
System.out.println(query.toString());
}
@Test
public void testBeanListHandler() throws SQLException {
// BeanHandler:适合取多行多列数据
QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
List<Employee> query2 = runner.query("select * from emp", new BeanListHandler<Employee>(Employee.class));
for (Employee employee : query2) {
System.out.println(employee);
}
}
@Test
public void testScalarHandler() throws SQLException {
// ScalarHandler:适合取单行单列数据
QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
Object query = runner.query("select count(*) from emp ", new ScalarHandler());
System.out.println(query);
}
@Test
public void testArrayHander() throws SQLException {
// ArrayHandler:适合取1条记录。把该条记录的每列值封装到一个数组中Object[]
QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
Object[] query = runner.query("select * from school where empno = ?", new ArrayHandler(), 1234);
for (Object object : query) {
System.out.println(object);
}
}
@Test
public void testArrayListHander() throws SQLException {
// ArrayHandler:适合取1条记录。把该条记录的每列值封装到一个数组中Object[]
QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
List<Object[]> query = runner.query("select * from emp ", new ArrayListHandler());
for (Object[] objects : query) {
for (Object object : objects) {
System.out.println(object);
}
}
}
@Test
public void testColumnListHander() throws SQLException {
// ColumnListHandler:取某一列的数据。封装到List中。
QueryRunner runner = new QueryRunner(DruidUtils.getDataSource());
List<Object> query = runner.query("select * from emp ", new ColumnListHandler<Object>(2));
for (Object objects : query) {
System.out.println(objects);
第六节 封装查询方法(扩展)
6.1 RowMapper接口方式
- 步骤1 定义封装规则接口
RowMapper
接口
//封装规则。封装对象为泛型,通用。参数需要的是ResultSet对象
public interface RowMapper<T> {
T getRow(ResultSet rs);
}
- 步骤2 DbUtils中增加查询方法
//查询通用方法 泛型方法。返回值为泛型集合,因为查询可能是单个或多个
//参数:sql语句、封装方式、参数列表
public static <T> List<T> executeQuery(String sql , RowMapper<T> mapper,Object... params){
PreparedStatement preparedStatement = null;
Connection connection = getConnection();
ResultSet rs = null;
//定义泛型集合,类型由封装方式的具体对象来决定
List<T> list=new ArrayList<T>();
try {
preparedStatement = connection.prepareStatement(sql);
//循环为占位符赋值
for(int i= 0;i<params.length;i++){
preparedStatement.setObject(i+1, params[i]);
}
rs = preparedStatement.executeQuery();
//迭代
while(rs.next()){
//获得一行数据,调用封装方式,得到对象,添加到集合中
list.add(mapper.getRow(rs));
}
return list;
} catch (SQLException e) {
e.printStackTrace();
}finally {
closeAll(rs, preparedStatement, connection);
}
return null;
}
- 步骤3:编写接口实现类
//定义具体的封装方式,决定通用查询封装的是具体某个对象
//遍历ResultSet,取值做对象属性的赋值
public class AccountRowMapper implements RowMapper<Account> {
@Override
public Account getRow(ResultSet rs) {
Account account = new Account();
try {
account.setId(rs.getInt(1));
account.setName(rs.getName(2));
account.setMoney(rs.getDouble(3));
} catch (SQLException e) {
e.printStackTrace();
}
return account;
}
}
6.2 反射方式
- 通用性更强,理解即可
//返回List集合的方法
public static <T> List<T> executeQuery(String sql, Class<T> class1,Object... params){
List<T> list=new ArrayList<>();
Connection conn=null;
PreparedStatement pstat=null;
ResultSet rs=null;
//查询数据
try {
conn=getConnection();
pstat = conn.prepareStatement(sql);
if(params!=null){
for (int i = 0; i < params.length; i++) {
pstat.setObject(i+1, params[i]);
}
}
rs = pstat.executeQuery();
//获取rs中的列名
ResultSetMetaData metaData = rs.getMetaData();
while(rs.next()){
//创建一个对象
T t=class1.newInstance();
for(int i=0;i<metaData.getColumnCount();i++){
String columnLabel=metaData.getColumnLabel(i+1);
Object value = rs.getObject(columnLabel);//empno ename job
//System.out.println(columnLabel+"==="+value);
//创建属性描述符
try {
PropertyDescriptor pd=new PropertyDescriptor(columnLabel, class1);
if(pd!=null){
//System.out.println(pd.getName());
Method writeMethod = pd.getWriteMethod(); //setEmpno setEname setJob
writeMethod.invoke(t,value);
}
} catch (Exception e) {
continue;
}
}
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
closeAll(conn, pstat, rs);
}
return list;
}
总结
-
三层架构 DAO、Service、UI Utils 工具类 domain实体类
-
JDBC操作事务,结合事务的三层架构,在DBUtils工具类里,封装对事务的一系列操作
-
ThreadLocal解决多个DAO操作,connection对象不同步的问题。
-
Apache DBUtils的使用,QueryRunner ResultSetHanlder 接口
-
扩展理解
作业题
- 使用三层架构重构学生管理系统,要求实现正删改查,基于控制台实现即可
要求可以对学生信息进行添加、修改、删除、查询的功能
面试题
- 什么三层架构?
- JDBC事务控制方法有那些
上一篇: 软文怎么发布推广效果更好 记住这5点要求
下一篇: 电话本管理系统java实现