JDBC day03

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{
    //使用jdbc6步实现接口中每一个方法
}

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{
    //定义对t_user表所有的功能方法
    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){
        //调用dao + 逻辑判断
    }
    public void login(String name,String password){
        //调用dao + 逻辑判断
    }
}

1.4 视图层(了解)

视图层(view):负责功能的入口(接收数据)以及功能执行后结果的显示。

视图类:

  • 一个功能一个视图类
  • 视图类名 = 功能+View
  • 视图类中代码 = 调用业务层方法+ 输出 + Scanner语句
  • 所有的视图类都要定义在view包下
package com.baizhi.view;
public class LoginView{
    public static void main(String[] args){
        //使用输出语句提示用户
        //使用Scanner接收数据
        
        //调用业务层方法
        
        //使用输出语法,打印功能结果
    }
}

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);
			//调用dao + 业务逻辑
			...
			//成功-->提交事务
			conn.commit();
        }catch(Exception e){
        	conn.rollback();
        	e.printStatckTrace();
        	throw new RuntimeException(e);
         }finally{
            	JDBCUtils.close(conn,null,null);
          }
	}
}

实战

  1. 建表

    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);
    
  2. 实体类 (省略)

  3. dao (省略)

  4. service

public class AccountServiceImpl implements AccountService {


	@Override
	public void transfer(String from, String to, double money) {
		// TODO Auto-generated method stub
		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) {
				// TODO Auto-generated catch block
				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层中

  1. dao层每个方法添加一个Connection形参
  2. 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);
            //调用dao方法
            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 {
			//通过流读取jdbc.properties文件中内容
			//通过类对象的getResourceAsStream()获取配置文件输入流
//			InputStream in = new FileInputStream("./src/jdbc.properties");
			// /表示com超级父包所在的目录 
			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);
		}
	}
	
	//抽取JDBC前2步
	public static Connection getConnection() {
		
		//从当前线程空间中获取conn
		Connection  conn = tl.get();
		
		//如果获取不到,说明是第1次获取连接,则从数据库中获取,然后保存到线程中
		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);
			}
		}
        //conn不为空,说明不是第1次,则直接返回从线程对象中获取的连接
		return conn;
	}
	//抽取JDBC最后1步
	public static void close(Connection conn,PreparedStatement pstm,ResultSet rs) {
		if(rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(pstm != null) {
			try {
				pstm.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn != null) {
			try {
				conn.close();
                //连接关闭后,要从线程对象中移除连接
				tl.remove();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				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框架的步骤:

  1. 项目中引入JUnit4框架(2个jar包在资料 中可以找到)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73Mh1OQ2-1631064948240)(JDBC day03.assets/image-20210513093956656.png)]

  2. 使用框架

    在一个测试类中,定义多个测试方法,使用@Test描述普通方法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vLsVTcoi-1631064948241)(JDBC day03.assets/image-20210513093934353.png)]

使用要求:

  1. 测试类必须是公开类

  2. 测试方法必须是公开的非静态的无参的无返回值的方法

    最最简单的普通方法

  3. 注意:同包下尽量不要定义名为Test的类,避免混淆

    要运行的测试方法一定要添加@Test描述

开发规范:

​ dao+service都应该测试,至少应该使用JUnit测试dao中的每个方法

4 JDBC项目的开发步骤

  1. 搭建开发环境

    1. 新建一个项目
    2. 导入jdbc驱动jar包:新建一个名为lib的目录,将ojdbcx.jar复制到lib目录下,邮件选中ojdbcx.jar,build path–>add to build path
    3. 提供JDBCUtils 和配置文件(允许复制粘贴)
  2. 建表

  3. 实体类:entity包

  4. dao dao接口+dao实现类

  5. service service接口+service实现类

    service实现类的方法一定要加事务控制

  6. test

    因为现在的view在日后会被替换,所以view在这里不需要练习,使用test代替即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUkLr8o6-1631064948242)(JDBC day03.assets/1621937062146.png)]

要定义名为Test的类,避免混淆

要运行的测试方法一定要添加@Test描述

开发规范:

​ dao+service都应该测试,至少应该使用JUnit测试dao中的每个方法

4 JDBC项目的开发步骤

  1. 搭建开发环境

    1. 新建一个项目
    2. 导入jdbc驱动jar包:新建一个名为lib的目录,将ojdbcx.jar复制到lib目录下,邮件选中ojdbcx.jar,build path–>add to build path
    3. 提供JDBCUtils 和配置文件(允许复制粘贴)
  2. 建表

  3. 实体类:entity包

  4. dao dao接口+dao实现类

  5. service service接口+service实现类

    service实现类的方法一定要加事务控制

  6. test

    因为现在的view在日后会被替换,所以view在这里不需要练习,使用test代替即可。

[外链图片转存中…(img-QUkLr8o6-1631064948242)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值