JDBC day03
1 三层架构
在软件开发的工程实践中,分解复杂的软件系统时,软件工程最常见的设计方式就是分层。将整体系统拆分成N个层次,每个层次有独立的职责,多个层次协同提供完整的功能。分层的好处:简化设计、各司其职、更容易扩展。而三层架构是JavaEE规范推荐的架构,在JavaEE的开发中,三层架构具体分为表示层(UI、web层)、业务逻辑层(service层)、数据访问层(dao层)。
1.1 分层的设计思路
现有代码没有分层的,我们首先来看现有代码存在的问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pm6bkE4X-1631064948233)(JDBC day03.assets/image-20210512125521306.png)]
现有程序的问题:
程序的3个部分杂糅在一起,违反单一职责原则,不利于后期代码的维护。
**解决方案:**分层
分层的思路:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubBJphNF-1631064948234)(JDBC day03.assets/1578302138324.png)]
1.2 数据访问层
数据访问层(dao):用于和数据库直接打交道。
dao接口:
- 一张表一个dao接口
- 接口名和表名相关
- dao接口中封装对一张表的增、删、改、查操作方法
- 所有的dao接口,定义在dao包下
dao实现类:
- dao实现类用于实现dao接口
- 使用JDBC6步代码实现具体操作
- 所有的dao实现类定义在dao.impl包下
示例:
package com.baizhi.dao;
public interface XxxDao{
public void insertXxx(Xxx x);
public void deleteXxxById(Integer id);
public void updateXxx(Xxx x);
public Xxx selectXxxById(Integer id);
public List<Xxx> selectAllXxxs();
}
package com.baizhi.dao.impl;
public class XxxDaoImpl implements XxxDao{
}
1.3 业务层
业务层(service):根据功能的业务要求,实现具体功能的代码。通常业务实现由dao的调用以及一些逻辑判断代码组成。
service接口
- 一张表一个service接口
- service接口中定义表中所有功能方法
- service接口名和表名相关,t_xxx --> XxxService
- 所有的service接口定义在service包下
service实现类
- service实现类用于实现service接口
- 实现类名=service接口名+Impl
- 实现类方法实现:调用dao+业务逻辑判断
- 所有的service实现类定义在service.impl包下
package com.baizhi.service;
public interface UserService{
public void register(String name,String password);
public boolean login(String name,String password);
}
package com.baizhi.service.impl;
public class UserServiceImpl implements UserService{
public void register(String name,String password){
}
public void login(String name,String password){
}
}
1.4 视图层(了解)
视图层(view):负责功能的入口(接收数据)以及功能执行后结果的显示。
视图类:
- 一个功能一个视图类
- 视图类名 = 功能+View
- 视图类中代码 = 调用业务层方法+ 输出 + Scanner语句
- 所有的视图类都要定义在view包下
package com.baizhi.view;
public class LoginView{
public static void main(String[] args){
}
}
1.5 完整示例
dao层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4TsLqit3-1631064948235)(JDBC day03.assets/image-20210512130946596.png)]
service层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tq7exAyp-1631064948235)(JDBC day03.assets/image-20210512131035086.png)]
view层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-itj6GfRi-1631064948236)(JDBC day03.assets/image-20210512131126184.png)]
小结: JDBC功能开发步骤:
1. 建表
2. 实体类
3. dao
4. service
5. view
2 事务控制
2.1 事务回顾
事务:保证一个业务操作完整性的数据库机制。保证一个业务功能包含的多条SQL要么同时成功,要么同时失败。JDBC中需要在业务层方法中进行事务控制:
service层中一个方法对应着一个功能,一个功能对应的多条SQL就在service方法中执行(多次调用dao)。为保证业务操作完整性,也就要保证业务层一个方法在执行多条SQL时要么同时成功,要么同时失败。
比如:转账功能需要2条更新语句,在service层中对应着transfer方法,业务方法中就需要保证2条SQL要么同时成功,要么同时失败!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qpQi7SJ8-1631064948236)(JDBC day03.assets/image-20210512212304107.png)]
注意:实战开发中,无论业务层方法有多简单,都一定要添加事务控制。
2.2 JDBC事务控制案例:转账
业务层事务控制的标准结构:
业务层:
class XxxServiceImpl{
public void 业务方法(){
try{
conn.setAutoCommit(false);
...
conn.commit();
}catch(Exception e){
conn.rollback();
e.printStatckTrace();
throw new RuntimeException(e);
}finally{
JDBCUtils.close(conn,null,null);
}
}
}
实战:
-
建表 create table t_account(
account_id int primary key auto_increment,
account_name varchar(20) not null,
balance decimal(10,2) not null
);
insert into t_account values (1,'xushy',100.0);
insert into t_account values (2,'liy',100000.0);
-
实体类 (省略) -
dao (省略) -
service
public class AccountServiceImpl implements AccountService {
@Override
public void transfer(String from, String to, double money) {
Connection conn = JDBCUtils.getConnection();
try {
conn.setAutoCommit(false);
AccountDao dao = new AccountDaoImpl();
Account fromAccount = dao.selectAccountByName(from);
Account toAccount = dao.selectAccountByName(to);
if(fromAccount == null || toAccount == null) {
throw new RuntimeException("转出账户或者转入账户不存在");
}
toAccount.setBalance(toAccount.getBalance()+money);
fromAccount.setBalance(fromAccount.getBalance()-money);
dao.updateAccount(toAccount);
dao.updateAccount(fromAccount);
if(fromAccount.getBalance() < 0) {
throw new RuntimeException("转出账户余额不足");
}
conn.commit();
}catch(Exception e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
throw new RuntimeException(e);
}finally {
JDBCUtils.close(conn, null, null);
}
}
}
2.3 事务控制
执行SQL的连接和事务控制的连接必须是同1个连接。 当前业务层方法事务控制的问题:没有保证业务层和dao层使用同1个连接。要保证业务操作的完整性,必须解决service和dao使用同1连接的问题。
2.3.1 方案1:连接注入
连接注入的思路:将service层创建的连接通过dao参数传递到dao层中
- dao层每个方法添加一个Connection形参
- dao实现类中不在主动获取连接,service在调用dao的方法时,将业务层获取的连接注入到dao层
dao层方法声明
package com.baizhi.dao;
public interface XxxDao{
public void insertXxx(Xxx x,Connection conn);
public void deleteXxxById(Integer id,Connection conn);
public void updateXxx(Xxx x,Connection conn);
public Xxx selectXxxById(Integer id,Connection conn);
public List<Xxx> selectAllXxxs(Connection conn);
}
service层实现类
package com.baizhi.service.impl
public class XxxServiceImpl implements XxxService{
public void 业务方法(){
Connection conn = JDBCUtils.getConnection();
try{
conn.setAutoCommit(false);
XxxDao dao = new XxxDaoImpl();
dao.insertXxx(x,conn);
dao.deleteXxxById(id值,conn)
...
conn.commit();
}catch(Exception e){
conn.rollback();
}finally{
JDBCUtils.close(conn,null,null);
}
}
}
注意:业务层和dao层使用同1个连接后,dao层不再关闭连接,而是统一在service中关闭连接。
存在的问题:
- 定义繁琐,开发效率低
- API污染:无形之中和原生的JDBC技术耦合,不易于后期技术的更迭
2.3.2 方案2:ThreadLocal
ThreadLocal的思路:通过线程对象将service创建的连接传递到dao层中。
线程知识回顾:
Thread: 进程中运行的一段连续的代码。一段连续的运行的代码背后一定有一个线程在负责执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0D6pORX-1631064948237)(JDBC day03.assets/image-20210512220029411.png)]
结论:嵌套方法调用在运行时,嵌套方法和被嵌套方法是由同1个线程执行,对应着同1个线程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3O8HDJ7-1631064948237)(JDBC day03.assets/image-20210512230010922.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q7KagLA8-1631064948238)(JDBC day03.assets/image-20210512222650694.png)]
线程对象中有一块存储空间,可以用来保存数据(比如连接),在线程的执行流程中所涉及的代码都可以操作(保存和读取)这块存储空间。那么,service层将创建的连接保存到线程空间中后,dao层中可以从线程空间中获取到这个连接。 方案:就可以使用Thread对象在service和dao层传递连接,解决service层和dao层使用同1连接的问题。
如何操作当前线程对象的存储空间?
ThreadLocal:操作线程存储空间的工具,该类型的对象使用当前线程对象的存储空间保存、获取、删除数据。
线程对象的存储空间没有直接暴露出来,也就是不能直接通过线程对象(比如:Thread.currentThread().空间 = 值)操作其内部空间。需要通过专门的工具(ThreadLocal类型的对象)操作线程空间。
向线程存储空间存数据: tl.set(数据); 从线程存储空间取数据:tl.get(); 从线程存储空间溢出数据:tl.remove();
ThreadLocal对象就好比求职例子中的负责将简历保存到求职者身上的HR。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l63ZuiHj-1631064948238)(JDBC day03.assets/image-20210512231136734.png)]
使用ThreadLocal保证业务层和dao层使用同1连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D95tO6uL-1631064948239)(JDBC day03.assets/image-20210512230609789.png)]
2.3.3 JDBCUtils最终版
package com.baizhi.util;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private static Properties prop = new Properties();
static {
try {
InputStream in = JDBCUtils.class.getResourceAsStream("/jdbc.properties");
prop.load(in);
in.close();
String driverClassName = prop.getProperty("driverClassName");
Class.forName(driverClassName);
System.out.println("driverClassName="+driverClassName);
}catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static Connection getConnection() {
Connection conn = tl.get();
if(conn == null) {
try {
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String password = prop.getProperty("password");
System.out.println("url="+url);
System.out.println("user="+user);
System.out.println("password="+password);
conn = DriverManager.getConnection(url, user, password);
tl.set(conn);
}catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
return conn;
}
public static void close(Connection conn,PreparedStatement pstm,ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pstm != null) {
try {
pstm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
tl.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3 JUnit测试框架
为了尽可能减少项目上线后出现bug,在开发的时候一定要对代码进行充分的测试。JDBC中主要是dao和service要进行充分的测试,当前测试环节的问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUL6OTLy-1631064948239)(JDBC day03.assets/image-20210513091501304.png)]
一个要测试的方法就得有一个测试类,测试类数量非常庞大,难以管理。
解决方案:JUnit测试框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2zzpGXM-1631064948240)(JDBC day03.assets/image-20210513094537322.png)]
使用思路:使用@Test注解描述普通方法,即可使得该方法拥有和main函数一样的运行能力。
使用JUnit4框架的步骤:
-
项目中引入JUnit4框架(2个jar包在资料 中可以找到) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73Mh1OQ2-1631064948240)(JDBC day03.assets/image-20210513093956656.png)] -
使用框架 在一个测试类中,定义多个测试方法,使用@Test描述普通方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vLsVTcoi-1631064948241)(JDBC day03.assets/image-20210513093934353.png)]
使用要求:
-
测试类必须是公开类 -
测试方法必须是公开的非静态的无参的无返回值的方法
最最简单的普通方法
-
注意:同包下尽量不要定义名为Test的类,避免混淆 要运行的测试方法一定要添加@Test描述
开发规范:
? dao+service都应该测试,至少应该使用JUnit测试dao中的每个方法
4 JDBC项目的开发步骤
-
搭建开发环境
- 新建一个项目
- 导入jdbc驱动jar包:新建一个名为lib的目录,将ojdbcx.jar复制到lib目录下,邮件选中ojdbcx.jar,build path–>add to build path
- 提供JDBCUtils 和配置文件(允许复制粘贴)
-
建表 -
实体类:entity包 -
dao dao接口+dao实现类 -
service service接口+service实现类 service实现类的方法一定要加事务控制 -
test
因为现在的view在日后会被替换,所以view在这里不需要练习,使用test代替即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUkLr8o6-1631064948242)(JDBC day03.assets/1621937062146.png)]
要定义名为Test的类,避免混淆
要运行的测试方法一定要添加@Test描述
开发规范:
? dao+service都应该测试,至少应该使用JUnit测试dao中的每个方法
4 JDBC项目的开发步骤
-
搭建开发环境
- 新建一个项目
- 导入jdbc驱动jar包:新建一个名为lib的目录,将ojdbcx.jar复制到lib目录下,邮件选中ojdbcx.jar,build path–>add to build path
- 提供JDBCUtils 和配置文件(允许复制粘贴)
-
建表 -
实体类:entity包 -
dao dao接口+dao实现类 -
service service接口+service实现类 service实现类的方法一定要加事务控制 -
test
因为现在的view在日后会被替换,所以view在这里不需要练习,使用test代替即可。
[外链图片转存中…(img-QUkLr8o6-1631064948242)]
|