Java业务设计分析
项目分层设计
项目分层设计
- 一个完整的项目设计中不仅仅是实现项目的核心需求,同时也需要具备有良好的设计分层,每一层都拥有各自所需要完成的核心功能,并且彼此之间没有任何的强耦合关联,该图中将整个的项目结构划分为了三个不同的层次:
- 业务中心层:是项目的核心所在,封装了用户所有可能存在的请求处理操作,例如:用户注册、用户登录就属于两个不同的操作业务,而这些业务所涉及到的数据库操作部分全部要封装在业务中心里
- 业务客户端层:提供了用户请求的处理与服务端页面响应操作,是 JavaWEB 开发所需要完成的核心功能;
- 持久化存储层:提供了数据持久化存储,包括 SQL 结构化存储或者是 NoSQL 存储。
业务结构模型
现代项目的开发都是以 SQL 数据库作为终端存储介质,所有的相关业务的处理数据全部都保存在 SQL 数据库之中,而对于数据库的开发操作全部都是基于 SQL 语句完成的,所以就需要在数据层中将所有可能使用到的 SQL 操作封装为具体的处理方法,而业务层在进行处理时实际上是不关心具体的 SQL 如何执行的,仅仅关注的是数据层中的方法能否完成指定的功能,开发者直接依据业务层就可以完成若干张数据表的数据操作
分层设计实例
项目开发流程
一个完整的项目中,需要根据用户的需求来进行业务的设计而后再根据业务需要设计出相应的数据表结构,这样在进行程序开发中就可以根据业务需要再进行数据层和业务层的结构拆分
DROP DATABASE IF EXISTS yootk ;
CREATE DATABASE yootk CHARACTER SET UTF8 ;
USE yootk ;
CREATE TABLE member(
mid VARCHAR(50) ,
name VARCHAR(50) ,
age INT ,
email VARCHAR(50) ,
sex VARCHAR(10) ,
birthday DATE ,
note TEXT ,
CONSTRAINT pk_mid PRIMARY KEY(mid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
程序类与数据表映射
VO 类映射转换
在进行分层设计中,每个层彼此之间都是独立的,每个层对外暴露的只有操作接口,但是不同层之间依然需要进行数据的传输,所以此时的做法是根据数据表的结构来设计一个 VO(Value Object)类,而后不同层之间依靠 VO 类来进行数据的传递处理
简单 Java 类开发原则
- 简单 Java 类可以与数据表之间产生合理的映射关系,同时 java 中提供的数据类型可以直接与 SQL 数据库中的数据类型对应,这样针对于项目中的简单 java 类的开发请遵循如下的若干原则:
- 一个项目中的数据库中会存在有多张实体表,所以也就会映射为多个简单 Java 类,所有的简单 Java 类可以统一保存在 vo 包中,如果父包名称为“com.yootk”,则 VO 类的保存包名称为“com.yootk.vo”
- 每一张实体表一定要有一个与之对应的简单 Java 类,关系表根据情况来动态判断,如果包含有实体字段则需要创建对应的 Java 类,如果全部由关联字段所组成,则可以根据需要进行创建;
- 考虑到序列化传输的需求,简单类一定要实现 java.io.Serializable 父接口:类中的所有属性一定要进行封装,封装之后的属性一定要定义有 setter、getter 方法
- 类中可以根据需要定义若干个构造方法,但是一定要保留有一个无参构造方法;
- 类中的属性不允许使用基本数据类型,属性类型统一为包装类,这样可以方便的描述'null”数据:
- 如果没有使用到一些特殊的结构的话不需要考虑 Comparable、toString()、hashCode()、equals()。
package com.yootk.vo;
import java.io.Serializable;
import java.util.Date;
public class Member implements Serializable { // 允许序列化
private String mid;
private String name;
private Integer age; // 包装类
private String email;
private String sex;
private Date birthday;
private String note;
public String getMid() {
return mid;
}
public void setMid(String mid) {
this.mid = mid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
数据层简介
数据层
- 数据层(Data Access Object、DAO)是整个项目开发中唯一一个与 JDBC 操作有关的程序类;
- 在数据层实现过程中不需要考虑具体的业务逻辑关系,更不需要考虑到数据库的事务处理操作,只需要使用 JDBC 标准中提供的 Connection、PreparedStatement、ResultSet 等接实现数据库的 CRUD 功能即可
- ORMapping(Object Relational Mapping、简称 ORM,或 O/RM 或 O/R Mapping)是一种基于面向对象设计思想实现的数据库操作开发者通过对象即可实现 SQL 命令的执行
1、
package com.yootk.util.dbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseConnection { // 数据库连接管理类
public static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
public static final String DBURL = "jdbc:mysql://localhost:3306/yootk";
public static final String USER = "root";
public static final String PASSWORD = "mysqladmin";
private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
private DatabaseConnection() {} // 构造方法私有化
public static Connection rebuildConnection() { // 重新创建数据库连接
Connection conn = null ;
try {
Class.forName(DBDRIVER);
conn = DriverManager.getConnection(DBURL, USER, PASSWORD);
} catch (Exception e){
e.printStackTrace();
}
return conn;
}
public static Connection getConnection() {
Connection conn = THREAD_LOCAL.get(); // 获取当前线程中的连接对象
if (conn == null) { // 没有连接对象
conn = rebuildConnection(); // 建立数据库连接
THREAD_LOCAL.set(conn); // 保存连接对象
}
return conn;
}
public static void close() {
Connection conn = THREAD_LOCAL.get(); // 获取当前线程中的连接对象
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
THREAD_LOCAL.remove(); // 清空ThreadLocal
}
}
}
2、
package com.yootk.common.dao.abs;
import com.yootk.util.dbc.DatabaseConnection;
import java.sql.Connection;
import java.sql.PreparedStatement;
public abstract class AbstractDAO { // 这个类是一个不完整的类
protected PreparedStatement pstmt; // 作为一个公共属性存在
protected Connection connection; // 作为一个公共属性存在
// 按照面向对象的设计原则,所有的子类在进行对象实例化时都一定要调用父类构造
public AbstractDAO() { // 无参构造方法
this.connection = DatabaseConnection.getConnection(); // 获取数据库连接
}
}
数据层接口标准
IMemberDAO
在完整的项目设计中,针对于数据表可能存在有大量的 SQL 操作,这样就有可能在每一个 DAO 接口中定义有大量的数据操作方法,所以在编写 DAO 方法时可以按照如下约定形式进行定义
- 数据更新操作:建议使用“doXxx()“形式进行命名,例如:"doCreate()”、"doEdit()"、"doRemove()"
- 数据查询操作:建议使用“findXxx()“、、"findByXxx()”命名,例如:findByld()、findAll()、findsplit();
- 数据统计操作:建议使用“getXxx()”命名,例如:getAlCount()。
1、
package com.yootk.dao;
public interface IMemberDAO { // 数据层接口
}
2、
package com.yootk.common.dao.base;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
/**
* 定义数据操作的公共接口,利用该接口封装公共的数据处理方法
* @param <ID> 描述不同数据表的主键类型
* @param <VO> 描述不同数据表映射转换的VO类型
* @author 李兴华(爆可爱的小李老师、沐言科技:www.yootk.com)
*/
public interface IBaseDAO<ID, VO> { // 描述公共接口
/**
* 实现数据信息的增加处理操作,该方法的主要功能是执行INSERT命令
* @param vo 要增加数据所保存的VO对象实例
* @return 数据增加成功返回true,否则返回false
* @throws SQLException 当前进行的是SQL数据库操作,所以所有的异常全部都为SQLException
*/
public boolean doCreate(VO vo) throws SQLException;
/**
* 实现数据修改操作处理,该方法的主要功能是执行UPDATE命令,此时根据主键修改数据
* @param vo 要更改数据的VO对象实例
* @return 数据修改成功返回true,否则返回false
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public boolean doEdit(VO vo) throws SQLException;
/**
* 实现数据的批量删除操作,在删除的时候要求将所有要删除的数据ID封装在Set集合之中
* @param ids 要删除的id集合
* @return 删除指定数量的数据之后返回true,否则返回false
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public boolean doRemove(Set<ID> ids) throws SQLException;
/**
* 根据ID查询完整数据信息,所有的信息通过VO实例进行包装
* @param id 要查询的主键内容
* @return 如果查询到指定的主键存在,则将数据以VO对象的实例形式返回,否则返回null
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public VO findById(ID id) throws SQLException;
/**
* 查询全部的数据信息
* @return 全部数据的List集合,如果该表没没有任何数据返回,集合为空集合(不是null,而是empty、集合长度为0)
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public List<VO> findAll() throws SQLException;
/**
* 实现数据分页加载
* @param currentPage 当前所在页
* @param lineSize 每页显示的数据行数
* @return 全部数据的List集合,如果该表没没有任何数据返回,集合为空集合(不是null,而是empty、集合长度为0)
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public List<VO> findSplit(Integer currentPage, Integer lineSize) throws SQLException;
/**
* 实现数据分页加载,在进行加载时可以实现数据的模糊查询,查询中需要设置查询字段的名称
* @param currentPage 当前所在页
* @param lineSize 每页显示的数据行数
* @param column 要查询的数据列名称
* @param keyword 要查询的关键字
* @return 全部数据的List集合,如果该表没没有任何数据返回,集合为空集合(不是null,而是empty、集合长度为0)
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public List<VO> findSplit(Integer currentPage, Integer lineSize, String column, String keyword) throws SQLException;
/**
* 进行数据表数据量全部个数统计
* @return SQL中的count()统计函数的执行结果
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public Long getAllCount() throws SQLException;
/**
* 进行数据表数据量全部个数统计
* @param column 要查询的数据列名称
* @param keyword 要查询的关键字
* @return SQL中的count()统计函数的执行结果
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public Long getAllCount(String column, String keyword) throws SQLException;
}
3、
package com.yootk.dao;
import com.yootk.common.dao.base.IBaseDAO;
import com.yootk.vo.Member;
import java.sql.SQLException;
public interface IMemberDAO extends IBaseDAO<String, Member> { // 数据层接口
/**
* 根据Email地址获取用户信息
* @param email 要查询的email
* @return 如果有对应的email数据,则以VO对象的形式封装返回,如果没有则返回null
* @throws SQLException 数据库执行时所抛出的JDBC异常
*/
public Member findByEmail(String email) throws SQLException;
}
数据层实现类
数据层实现类
数据层标准接口中定义的一系列的抽象方法,最终将由接口子类来实现,由于在 AbstractDAO 抽象父类中已经通过 DatabaseConnection 工具类获取到了 Connection 接口实例,这样子类就可以直接利用该实例创建 PreparedStatement 实现数据库 SQL 操作
package com.yootk.dao.impl;
import com.yootk.common.dao.abs.AbstractDAO;
import com.yootk.dao.IMemberDAO;
import com.yootk.vo.Member;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class MemberDAOImpl extends AbstractDAO implements IMemberDAO { // 实现接口
// AbastractDAO抽象类构造方法之中已经获取到了数据库的连接实例
@Override
public boolean doCreate(Member member) throws SQLException {
String sql = "INSERT INTO member(mid,name,age,email,sex,birthday,note) " +
" VALUES(?,?,?,?,?,?,?) ";
super.pstmt = super.connection.prepareStatement(sql); // 实例化PreparedStatement接口
super.pstmt.setString(1, member.getMid());
super.pstmt.setString(2, member.getName());
super.pstmt.setInt(3, member.getAge());
super.pstmt.setString(4, member.getEmail());
super.pstmt.setString(5, member.getSex());
super.pstmt.setDate(6, new java.sql.Date(member.getBirthday().getTime()));
super.pstmt.setString(7, member.getNote());
return super.pstmt.executeUpdate() > 0; //' 有更新记录行数返回
}
@Override
public boolean doEdit(Member member) throws SQLException {
String sql = "UPDATE member SET name=?,age=?,email=?,sex=?,birthday=?,note=? WHERE mid=?";
super.pstmt = super.connection.prepareStatement(sql); // 实例化PreparedStatement接口
super.pstmt.setString(1, member.getName());
super.pstmt.setInt(2, member.getAge());
super.pstmt.setString(3, member.getEmail());
super.pstmt.setString(4, member.getSex());
super.pstmt.setDate(5, new java.sql.Date(member.getBirthday().getTime()));
super.pstmt.setString(6, member.getNote());
super.pstmt.setString(7, member.getMid());
return super.pstmt.executeUpdate() > 0; //' 有更新记录行数返回
}
@Override
public boolean doRemove(Set<String> strings) throws SQLException {
StringBuffer sql = new StringBuffer(30); // SQL操作的拼凑
sql.append("DELETE FROM member").append(" WHERE mid ").append(" IN (");
strings.forEach((id) -> sql.append("?,")); // 设置占位符
sql.delete(sql.length() - 1, sql.length()).append(")");
super.pstmt = super.connection.prepareStatement(sql.toString());
int foot = 1;
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
super.pstmt.setString(foot ++, iterator.next());
}
return super.pstmt.executeUpdate() == strings.size();
}
@Override
public Member findById(String s) throws SQLException {
Member vo = null; // 保存返回数据
String sql = "SELECT mid,name,age,email,sex,birthday,note " +
" FROM member WHERE mid=?";
super.pstmt = super.connection.prepareStatement(sql);
super.pstmt.setString(1, s);
ResultSet rs = super.pstmt.executeQuery();
if (rs.next()) { // 数据存在
vo = new Member();
vo.setMid(rs.getString(1));
vo.setName(rs.getString(2));
vo.setAge(rs.getInt(3));
vo.setEmail(rs.getString(4));
vo.setSex(rs.getString(5));
vo.setBirthday(rs.getDate(6));
vo.setNote(rs.getString(7));
}
return vo;
}
@Override
public List<Member> findAll() throws SQLException {
List<Member> all = new ArrayList<>();
String sql = "SELECT mid,name,age,email,sex,birthday,note FROM member";
super.pstmt = super.connection.prepareStatement(sql);
ResultSet rs = super.pstmt.executeQuery();
while (rs.next()) {
Member vo = new Member();
vo.setMid(rs.getString(1));
vo.setName(rs.getString(2));
vo.setAge(rs.getInt(3));
vo.setEmail(rs.getString(4));
vo.setSex(rs.getString(5));
vo.setBirthday(rs.getDate(6));
vo.setNote(rs.getString(7));
all.add(vo); // 数据保存在集合之中
}
return all;
}
@Override
public List<Member> findSplit(Integer currentPage, Integer lineSize) throws SQLException {
List<Member> all = new ArrayList<>();
String sql = "SELECT mid,name,age,email,sex,birthday,note " +
" FROM member LIMIT ?,?";
super.pstmt = super.connection.prepareStatement(sql);
super.pstmt.setInt(1, (currentPage - 1) * lineSize);
super.pstmt.setInt(2, lineSize);
ResultSet rs = super.pstmt.executeQuery();
while (rs.next()) {
Member vo = new Member();
vo.setMid(rs.getString(1));
vo.setName(rs.getString(2));
vo.setAge(rs.getInt(3));
vo.setEmail(rs.getString(4));
vo.setSex(rs.getString(5));
vo.setBirthday(rs.getDate(6));
vo.setNote(rs.getString(7));
all.add(vo); // 数据保存在集合之中
}
return all;
}
@Override
public List<Member> findSplit(Integer currentPage, Integer lineSize, String column, String keyword) throws SQLException {
List<Member> all = new ArrayList<>();
String sql = "SELECT mid,name,age,email,sex,birthday,note " +
" FROM member WHERE " + column + " LIKE ? LIMIT ?,?";
super.pstmt = super.connection.prepareStatement(sql);
super.pstmt.setString(1, "%" + keyword + "%");
super.pstmt.setInt(2, (currentPage - 1) * lineSize);
super.pstmt.setInt(3, lineSize);
ResultSet rs = super.pstmt.executeQuery();
while (rs.next()) {
Member vo = new Member();
vo.setMid(rs.getString(1));
vo.setName(rs.getString(2));
vo.setAge(rs.getInt(3));
vo.setEmail(rs.getString(4));
vo.setSex(rs.getString(5));
vo.setBirthday(rs.getDate(6));
vo.setNote(rs.getString(7));
all.add(vo); // 数据保存在集合之中
}
return all;
}
@Override
public Long getAllCount() throws SQLException {
String sql = "SELECT COUNT(*) FROM member";
super.pstmt = super.connection.prepareStatement(sql);
ResultSet rs = super.pstmt.executeQuery();
if (rs.next()) {
return rs.getLong(1);
}
return 0L; // 没有记录返回0
}
@Override
public Long getAllCount(String column, String keyword) throws SQLException {
String sql = "SELECT COUNT(*) FROM member WHERE " + column + " LIKE ?";
super.pstmt = super.connection.prepareStatement(sql);
super.pstmt.setString(1, "%" + keyword + "%");
ResultSet rs = super.pstmt.executeQuery();
if (rs.next()) {
return rs.getLong(1);
}
return 0L; // 没有记录返回0
}
@Override
public Member findByEmail(String email) throws SQLException {
Member vo = null; // 保存返回数据
String sql = "SELECT mid,name,age,email,sex,birthday,note " +
" FROM member WHERE email=?";
super.pstmt = super.connection.prepareStatement(sql);
super.pstmt.setString(1, email);
ResultSet rs = super.pstmt.executeQuery();
if (rs.next()) { // 数据存在
vo = new Member();
vo.setMid(rs.getString(1));
vo.setName(rs.getString(2));
vo.setAge(rs.getInt(3));
vo.setEmail(rs.getString(4));
vo.setSex(rs.getString(5));
vo.setBirthday(rs.getDate(6));
vo.setNote(rs.getString(7));
}
return vo;
}
}
数据层工厂类
获取数据层接口实例
如果业务层要想调用数据层的接口功能,则一定要首先获取到一个数据层的接口实例,而按照 Java 设计原则来讲,不同层之间不应该暴露接口子类,只允许暴露接口视图,所以需要为数据层创建一个工厂处理类,该类可以返回数据层接口对象实例
1、
member.dao=com.yootk.dao.impl.MemberDAOImpl
2、
package com.yootk.common.util.factory;
import java.util.ResourceBundle;
public class ObjectFactory { // 定义一个羝工厂类
private static final ResourceBundle DAO_RESOURCE = ResourceBundle.getBundle("com.yootk.resource.dao"); // DAO资源绑定
/**
* 根据指定的资源key获取对应DAO接口实例
* @param key dao.properties资源文件中注册的key名称
* @param clazz 是一种结构名称的标注
* @param <T> 泛型类型,因为此方法可以返回所有的DAO接口实例
* @return DAO对象实例,如果没有发现指定的key,返回null
*/
public static <T> T getDAOInstance(String key, Class<?>...clazz) {
String className = null; // 所有的注册资源都在资源文件之中,所以要读取内容并保存
try {
className = DAO_RESOURCE.getString(key); // KEY不存在查询出现异常
} catch (Exception e) {}
if (className == null || "".equals(className)) { // 没有数据
return null;
}
try { // 利用反射加载对象实例
return (T) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (Exception e) {
return null;
}
}
}
3、
package com.yootk.test.dao;
import com.yootk.common.util.factory.ObjectFactory;
import com.yootk.dao.IMemberDAO;
import com.yootk.vo.Member;
import java.util.Date;
public class TestDAO {
public static void main(String[] args) throws Exception {
IMemberDAO memberDAO = ObjectFactory.getDAOInstance("member.dao", IMemberDAO.class);
Member member = new Member();
member.setMid("yootk - " + Math.random());
member.setName("沐言优拓");
member.setEmail("muyan-" + Math.random() + "@yootk.com");
member.setSex("男");
member.setAge(16);
member.setBirthday(new Date());
member.setNote("沐言科技:www.yootk.com");
System.out.println(memberDAO.doCreate(member));
}
}
业务层简介
业务逻辑
业务层(Service、或者称为 BO“Business Object”)定义了一个项目中所需要的所有的功能集合,是一个完全独立的应用模块。在业务层中并不关心具体的数据层是如何工作的(可能是文件数据、数据库数据或者是其他网络数据),其所关心的只是能否有足够的数据可以实现业务逻辑处理
业务层实现结构
在进行业务层设计时,要根据业务的需求设计出相应的业务接口方法由于本次的操作是基于数据库完成的,所以在业务层的具体实现类中就要通过数据层暴露的 DAO 接口实现数据操作,这样在业务接口的实现子类中需要通过 ObiectFactory 类获取 DAO 接口实例
package com.yootk.common.service.abs;
public abstract class AbstractService {
public boolean checkAge(int age) { // 年龄范围检测
return age >= 18 && age <= 80;
}
public boolean checkSex(String sex) {
if (sex == null || "".equals(sex)) {
return false;
}
return sex.equals("男") || sex.equals("女");
}
/**
* 判断传入的数据内容是否为空值
* @param params 要判断的数据项
* @return 如果数据为空返回false,如果不为空返回true
*/
public boolean checkEmpty(String ... params) {
for (String param : params) {
if (param == null || "".equals(param)) {
return false;
}
}
return true;
}
}
业务层接口标准
业务接口
业务层设计中最为重要的一项就是接口标准的定义,在进行业务层方法定义时,一般都建议使用一些有意义的名称,例如更新操作的方法名称前缀一般为:addXxx0、editXxx()removeXxx(),而数据查询操作的前缀一般为:listXxx().getXxx()等,这样可以便于后续的程序控制
package com.yootk.service;
import com.yootk.vo.Member;
import java.util.List;
import java.util.Map;
public interface IMemberService { // 处理业务接口
/**
* 实现用户数据的添加操作,在进行数据添加时需要考虑到如下的业务验证:
* 1、【程序逻辑】判断输入的年龄是否符合要求;
* 2、【程序逻辑】判断输入的性别是否符合要求;
* 3、【数据层】检测输入的用户名(mid)数据是否重复,如果重复则不能够进行注册;
* 4、【数据层】检测输入的Email数据是否重复,如果重复则不能够进行注册;
* 5、【数据层】没有任何的数据验证问题,则执行数据增加操作。
* @param vo 包含有要增加的数据内容
* @return 数据增加成功返回true,否则返回false
* @throws Exception 业务有关的异常,正常的设计应该是做一些自定义的业务异常,本次简化操作
*/
public boolean add(Member vo) throws Exception;
/**
* 实现用户数据的更新处理,本次的更新是根据ID修改全部字段的内容,该操作需要执行如下业务逻辑:
* 1、【程序逻辑】判断输入的年龄是否符合要求;
* 2、【程序逻辑】判断输入的性别是否符合要求;
* 3、【数据层】判断更新后的email地址是否与其他的用户email相同
* 4、【数据层】执行数据更新处理操作
* @param vo 要更新全部数据的内容
* @return 更新成功返回true,否则返回false
* @throws Exception 抛出业务异常
*/
public boolean edit(Member vo) throws Exception;
/**
* 根据id实现数据的删除,在进行删除的时候需要执行如下的业务逻辑检测:
* 1、【程序逻辑】判断是否存在有要删除的数据内容(数组长度是否大于0)
* 2、【数据层】根据id进行数据的删除
* @param ids 要删除的用户id集合
* @return 删除成功返回true,否则返回false
* @throws Exception 抛出业务异常
*/
public boolean removeById(String ... ids) throws Exception;
/**
* 根据ID获取一个用户的完整信息
* @param id 要查询的id
* @return 数据以VO对象实例的形式返回,如果数据不存在则返回null
* @throws Exception 抛出业务异常
*/
public Member get(String id) throws Exception;
/**
* 实现用户的全部列表显示
* @return 用户的数据集合,如果没有任何的用户数据信息则列表长度为0(size()==0,不是null)
* @throws Exception 抛出业务异常
*/
public List<Member> list() throws Exception;
/**
* 实现用户数据的分页列表显示,也同时支持有数据的模糊查询
* @param currentPage 当前所在页
* @param lineSize 每页显示的数据行数
* @param column 模糊查询列
* @param keyword 查询关键字
* @return 数据的查询结果,该结果通过Map集合描述,在集合中保存有如下的数据内容:
* 1、key = allMembers、value = List集合,所有的查询结果;
* 2、key = allRecorders、value = Long统计结果(count()统计函数)
* @throws Exception 抛出业务异常
*/
public Map<String, Object> split(Integer currentPage, Integer lineSize, String column, String keyword) throws Exception;
}
业务层实现类
package com.yootk.service.impl;
import com.yootk.common.service.abs.AbstractService;
import com.yootk.common.util.factory.ObjectFactory;
import com.yootk.dao.IMemberDAO;
import com.yootk.service.IMemberService;
import com.yootk.vo.Member;
import java.util.*;
public class MemberServiceImpl extends AbstractService implements IMemberService {
// 业务层的实现依赖于数据层,所以要获取数据层接口的相关实例
private IMemberDAO memberDAO = ObjectFactory.getDAOInstance("member.dao", IMemberDAO.class);
@Override
public boolean add(Member vo) throws Exception {
if (!super.checkAge(vo.getAge())) { // 判断给定的年龄是否合法
return false;
}
if (!super.checkSex(vo.getSex())) { // 判断给定的性别是否合法
return false;
}
if (this.memberDAO.findById(vo.getMid()) == null) { // 无法获取指定id的数据
if (this.memberDAO.findByEmail(vo.getEmail()) == null) { // email地址不存在
return this.memberDAO.doCreate(vo); // 数据增加
}
}
return false; // 无法注册
}
@Override
public boolean edit(Member vo) throws Exception {
if (!super.checkAge(vo.getAge())) { // 判断给定的年龄是否合法
return false;
}
if (!super.checkSex(vo.getSex())) { // 判断给定的性别是否合法
return false;
}
// 判断当前更新的email数据是否存在
Member emailMember = this.memberDAO.findByEmail(vo.getEmail());
if (!emailMember.getMid().equals(vo.getMid())) { // 这个email地址不是当前用户的地址
return false;
} else {
return this.memberDAO.doEdit(vo);
}
}
@Override
public boolean removeById(String... ids) throws Exception {
if (ids.length == 0) { // 判断数组长度
return false;
}
// 防止可能设置了重复的删除用户id信息,所以可以将数据保存在Set集合之中
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList(ids)); // 将数组转为List,而后保存在Set集合中
return this.memberDAO.doRemove(set); // 数据删除
}
@Override
public Member get(String id) throws Exception {
return this.memberDAO.findById(id);
}
@Override
public List<Member> list() throws Exception {
return this.memberDAO.findAll();
}
@Override
public Map<String, Object> split(Integer currentPage, Integer lineSize, String column, String keyword) throws Exception {
Map<String, Object> map = new HashMap<>();
// 在使用LIKE进行数据查询时,由于存在有逐行匹配的问题,那么性能很差
if (super.checkEmpty(column, keyword)) { // 判断数据是否为空
map.put("allMembers", this.memberDAO.findSplit(currentPage, lineSize));
map.put("allRecorders", this.memberDAO.getAllCount());
} else { // 数据不为空
map.put("allMembers", this.memberDAO.findSplit(currentPage, lineSize, column, keyword));
map.put("allRecorders", this.memberDAO.getAllCount(column, keyword));
}
return map;
}
}
切面事务控制
业务对象与数据库连接
在本次的设计中全部的数据存储终端都是数据库,由于每一个业务处理都有可能执行多次数据库操作,所以就需要将数据库的连接与关闭处理机制全部交由业务层进行处理,即:在单主机的运行机制下,每一个业务方法的调用都只允许获取一次数据库连接
业务切面处理
由于每一个业务逻辑都有可能涉及到多张数据表的处理操作,那么当存在有数据更新业务处理的时候,为了保证数据操作的完整性,就必须进行有效的事务处理,然而事务处理本身并不属于核心业务的功能而仅仅是属于一个业务的处理切面,这样一来最佳的实现手段是直接通过动态代理设计模式来实现处理,
package com.yootk.common.service.proxy;
import com.yootk.util.dbc.DatabaseConnection;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.List;
public class ServiceProxy implements InvocationHandler { // 原生代理设计模式
private static final List<String> TRANSACTION_METHOD_HEAD = List.of(
"add", "create", "edit", "update", "delete", "remove", "change", "move");
private Object target; // 代理的真实对象实例
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; // 保存了返回结果
boolean trsancationFlag = this.openTransaction(method.getName()); // 获取事务开启的标记
if (trsancationFlag) { // 开启事务
DatabaseConnection.getConnection().setAutoCommit(false); // 取消自动提交
}
try {
result = method.invoke(this.target, args); // 方法的反射调用
if (trsancationFlag) {
DatabaseConnection.getConnection().commit(); // 提交事务
}
} catch (Exception e) {
if (trsancationFlag) {
DatabaseConnection.getConnection().rollback(); // 事务回滚
}
throw e;
} finally { // 最终一定要执行的部分
DatabaseConnection.close(); // 关闭数据库连接
}
return result;
}
/**
* 判断当前调用的方法名称是否要开启事务控制
* @param methodName 要执行的方法
* @return 如果需要开启事务则返回true,否则返回false
*/
private boolean openTransaction(String methodName) {
Iterator<String> iter = TRANSACTION_METHOD_HEAD.iterator();
while (iter.hasNext()) {
if (methodName.startsWith(iter.next())) {
return true; // 需要事务支持
}
}
return false; // 不需要开启事务
}
}
业务层工厂类
业务层工厂类
在进行业务调用时,外部可以获得的仅仅是一个业务接口的对象实例,而具体的子类实现应该是被隐藏的,同时在整个程序中还需要充分考虑到代理对象的引入,所以最佳的做法是在 ObjectFactory 工厂类中扩充一个可以获取业务接口实例的方法,而为了配置方便,也可以将所有的业务信息定义在"com.yootk.resource.service.properties”资源文件中,利用 ResourceBundle 进行加载
1、
member.service=com.yootk.service.impl.MemberServiceImpl
2、
package com.yootk.common.util.factory;
import com.yootk.common.service.proxy.ServiceProxy;
import java.util.ResourceBundle;
public class ObjectFactory { // 定义一个羝工厂类
private static final ResourceBundle DAO_RESOURCE = ResourceBundle.getBundle("com.yootk.resource.dao"); // DAO资源绑定
private static final ResourceBundle SERVICE_RESOURCE = ResourceBundle.getBundle("com.yootk.resource.service"); // SERVICE资源绑定
/**
* 获取指定的业务层对象实例(本质上是一个代理实例)
* @param key 要获取的key名称
* @param clazz 结构的标记
* @param <T> 返回业务层类型
* @return Service对象实例,如果没有指定的key存在,返回null
*/
public static <T> T getServiceInstance(String key, Class<?> ... clazz) {
String className = null; // 保存加载的类名称
try {
className = SERVICE_RESOURCE.getString(key); // 加载资源
} catch (Exception e) {}
if (className == null || "".equals(className)) {
return null;
}
try { // 最终返回的业务实例一定是代理包装过的实例
return (T) new ServiceProxy().bind(Class.forName(className).getDeclaredConstructor().newInstance());
} catch (Exception e) {
return null;
}
}
/**
* 根据指定的资源key获取对应DAO接口实例
* @param key dao.properties资源文件中注册的key名称
* @param clazz 是一种结构名称的标注
* @param <T> 泛型类型,因为此方法可以返回所有的DAO接口实例
* @return DAO对象实例,如果没有发现指定的key,返回null
*/
public static <T> T getDAOInstance(String key, Class<?>...clazz) {
String className = null; // 所有的注册资源都在资源文件之中,所以要读取内容并保存
try {
className = DAO_RESOURCE.getString(key); // KEY不存在查询出现异常
} catch (Exception e) {}
if (className == null || "".equals(className)) { // 没有数据
return null;
}
try { // 利用反射加载对象实例
return (T) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (Exception e) {
return null;
}
}
}
业务测试
代码测试
为了保证业务代码的执行正确,所以每当业务代码开发完成后实际上都需要进行完整的用例测试,以判断业务功能能否正常执行,如果测试通过则可以进行上线部署,反之则需要将代码错误反馈给开发人员进行修复
package com.yootk.test;
import com.yootk.common.util.factory.ObjectFactory;
import com.yootk.service.IMemberService;
import com.yootk.vo.Member;
import org.junit.jupiter.api.Assertions;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class TestMemberService {
private IMemberService memberService = ObjectFactory.getServiceInstance("member.service", IMemberService.class);
@org.junit.jupiter.api.Test
public void add1() throws Exception {
Member member = new Member();
member.setMid("yootk");
member.setName("沐言科技");
member.setAge(19);
member.setSex("男");
member.setBirthday(new Date());
member.setEmail("muyan@yootk.com");
member.setNote("沐言科技:www.yootk.com");
Assertions.assertTrue(this.memberService.add(member));
}
@org.junit.jupiter.api.Test
public void add2() throws Exception {
Member member = new Member();
member.setMid("yootk - " + Math.random()); // ID不同
member.setName("沐言科技");
member.setAge(19);
member.setSex("男");
member.setBirthday(new Date());
member.setEmail("muyan@yootk.com"); // Email 相同
member.setNote("沐言科技:www.yootk.com");
Assertions.assertTrue(this.memberService.add(member));
}
@org.junit.jupiter.api.Test
public void edit() throws Exception {
Member member = new Member();
member.setMid("yootk");
member.setName("李兴华");
member.setAge(19);
member.setSex("男");
member.setBirthday(new Date());
member.setEmail("muyan@yootk.com"); // Email 相同
member.setNote("沐言科技:www.yootk.com");
Assertions.assertTrue(this.memberService.edit(member));
}
@org.junit.jupiter.api.Test
public void removeById() throws Exception {
Assertions.assertTrue(this.memberService.removeById("yootk"));
}
@org.junit.jupiter.api.Test
public void get() throws Exception {
Member member = this.memberService.get("yootk");
System.err.println(member);
}
@org.junit.jupiter.api.Test
public void list() throws Exception {
List<Member> memberList = this.memberService.list();
memberList.forEach(System.out::println);
}
@org.junit.jupiter.api.Test
public void split1() throws Exception {
Map<String, Object> memberMap = this.memberService.split(1, 1, null, null);
System.out.println(memberMap);
}
@org.junit.jupiter.api.Test
public void split2() throws Exception {
Map<String, Object> memberMap = this.memberService.split(1, 1, "name", "李");
System.out.println(memberMap);
}
}
demo