目录
Java Web 后端的三层架构,即控制层(Controller)、服务层(Service)、数据访问层(DAO),是企业级应用开发中常用的设计模式。这种分层架构将应用程序的不同职责分离开来,以提高代码的可维护性、可扩展性和测试性。下面我们将深入探讨这三层的职责、实现细节以及它们之间的关系。
1. 控制层(Controller)
(1)职责及作用
控制层(Controller)是应用程序的入口,主要负责处理用户请求并返回响应。它直接与客户端(如浏览器、移动应用、第三方服务等)交互,通过接收 HTTP 请求、调用服务层来处理业务逻辑,并将处理结果(通常是视图或JSON数据)返回给客户端。控制层的核心职责可以归纳为以下几点:
1.接收并解析请求:处理来自客户端的 HTTP 请求,解析请求参数、头信息、路径变量等。
2.调用服务层:根据请求的类型(如获取数据、更新数据等),调用相应的服务层方法来处理具体的业务逻辑。
3.返回响应:将服务层处理后的结果封装成响应对象,并通过 HTTP 协议返回给客户端。响应可以是视图页面、JSON、XML、文件下载等。
4.处理异常:捕获在处理请求过程中可能发生的异常,并返回适当的错误信息或状态码。
(2)实现
在Spring框架中,控制层通常使用@Controller或@RestController注解。@Controller用于返回视图页面,而@RestController是@Controller和@ResponseBody的组合,主要用于RESTful API开发,返回的是JSON或XML数据。
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Integer id) {//id为路径参数
User user = userService.getUserById(id);//调用服务层获取用户信息
return Result.success(user); //返回json格式数据和用户信息
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User newUser = userService.saveUser(user);//调用服务层创建新用户
return Result.success(newUser);//返回json格式数据和新用户信息
}
//负责进行异常处理
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
UserController负责处理用户相关的请求。getUserById方法接收一个用户ID(用id接收),并调用UserService来获取用户数据,然后将数据返回给客户端。createUser方法接收一个用户对象,调用UserService处理创建新用户的操作。
Result类为自定义的响应数据类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应信息 描述字符串
private Object data; //返回的数据
//增删改 成功响应
public static Result success(){
return new Result(1,"success",null);
}
//查询 成功响应
public static Result success(Object data){
return new Result(1,"success",data);
}
//失败响应
public static Result error(String msg){
return new Result(0,msg,null);
}
}
(3)总结
控制层(Controller):负责接收请求,调度服务层并返回结果。主要是处理HTTP请求和响应。
2. 服务层(Service)
(1)职责与作用
服务层(Service)是整个应用程序的核心部分,负责处理所有的业务逻辑。它在控制层和数据访问层之间起到中介作用,封装了业务流程,并协调DAO层的数据访问操作。服务层的职责包括:
1.业务逻辑实现:将业务需求转化为可执行的逻辑操作,包括数据的校验、转换、计算等。
2.事务管理:在处理涉及多个数据库操作的业务时,确保这些操作要么全部成功,要么全部失败(即事务的原子性)。
3.调用DAO层:与数据访问层(DAO层)交互,完成数据的增删改查等各种操作。
4.封装复杂逻辑:将复杂的业务流程封装在服务层,控制层只需要调用相应的服务方法,而不关心其内部实现细节。
5.集成其他服务:在大型系统中,服务层可能还负责与外部服务的集成,例如调用其他微服务、消息队列、第三方API等。
(2)实现
服务层通常使用@Service注解来标识,它可以独立于控制层进行单元测试。典型的服务层类通常包含多个业务方法,这些方法通过注入DAO层对象(通过注解@Autowired 和 @Resource实现,了解其区别可移步http://t.csdnimg.cn/Sjc9D)来完成数据操作。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public User getUserById(Integer id) {
return userDao.getById(id);//调用Dao层通过ID查找用户
}
@Transactional
public User saveUser(User user) {
//业务逻辑部分代码:如检查用户名是否已存在,设置用户创建时间等
return userDao.save(user);
}
//其他处理方法代码
}
在这个UserService类中,我们实现了根据ID查找和保存用户的相关业务逻辑部分代码。另外@Transactional注解用于管理事务,确保该注解下的方法内的数据库操作要么全部成功,要么全部回滚。
(3)总结
服务层(Service):负责业务逻辑的处理和事务管理,是应用的核心层。
3. 数据访问层(DAO)
(1)职责及作用
数据访问层(DAO:Data Access Object)负责直接与数据库进行交互。它封装了所有与数据库相关的操作,主要职责如下:
1.CRUD 操作:提供增删改查数据库记录的操作。
2.数据库连接管理:管理与数据库的连接以及连接池的使用(在现代框架中,这通常由ORM工具或框架自动管理)。
3.数据转换:将数据库中的数据转换为应用程序中的对象(DTO,Data Transfer Object),以及将对象转换为适合存储在数据库中的格式。
4.查询封装:将复杂的数据库查询封装为易于调用的方法,因此控制层(Controller)和服务层(Service)不会直接接触SQL语句,即不必关心具体的数据库实现。
(2)实现
DAO层通常使用@Repository注解,并且依赖于ORM(如Hibernate或JPA)来简化数据访问。DAO层可以直接使用JPA的JpaRepository接口,或者自定义查询方法。
@Repository
public interface UserDao extends JpaRepository<User, Long> {
// 继承JpaRepository后,自动获得基本的CRUD操作
// 自定义查询方法
User getById(Integer id);
// 自定义的JPQL或Native SQL查询
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);
}
如上,UserDao类继承了JpaRepository接口,因此无需手动编写基本的CRUD方法。同时可以通过定义方法签名的方式来自动生成查询语句,或者使用@Query注解来编写自定义查询。
(3)总结
数据访问层(DAO):负责数据的持久化,与数据库直接交互,执行CRUD操作。
4. 各层之间的关系
Controller 层-- >Service 层-- >DAO 层:
控制层负责接收客户端请求并传递给服务层,服务层在处理业务逻辑后,可能需要访问数据,于是调用DAO层进行数据库操作。
DAO 层-->Service 层-- > Controller 层:
DAO层将数据返回给服务层,服务层进行必要的处理后,将结果返回给控制层,最终控制层将响应发送给客户端。
松耦合设计:每一层都只与它的下一级直接交互,这种设计使得代码更加模块化和可维护。每一层都高度封装,如果业务逻辑改变,只需修改服务层;如果数据库结构改变,只需修改DAO层即可,无需大动干戈。
5. 三层架构的优势
1.职责分离:将不同的职责分布到不同的层中,使得每一层的职责单一且明确。
2.可维护性:由于每一层都是独立的模块,维护和修改代码时可以做到“牵一发而不动全身”。
3.可测试性:可以针对每一层分别进行单元测试。例如在不涉及数据库的情况下测试服务层的业务逻辑。
4.可扩展性:可以很便捷地扩展功能。例如引入新的服务或数据源时,只需增加新的Service或DAO类,而不影响现有的代码。
5.松耦合:各层之间通过接口和依赖注入进行通信,降低了代码的耦合性。
6. 实际项目中的应用
在实际项目中,三层架构通常配合其他设计模式和架构模式一起使用:
1.DTO(Data Transfer Object)模式:在控制层和服务层之间使用DTO来传递数据,避免直接暴露实体类。
2.AOP(面向切面编程):用于日志记录、权限检查、事务管理等横切关注点,与三层架构无缝集成。
3.Spring Boot:通过简化配置和集成依赖,使三层架构的实现更加便捷。
7.总结——三层架构
三层架构在实际项目中不仅有助于组织代码结构,确保清晰的职责划分,还极大地提升了代码的可维护性、可扩展性和测试性。它使得应用程序的开发、维护和扩展变得更加容易,是Java Web开发中的一项最佳实践。通过灵活应用三层架构,开发者可以构建出健壮、可扩展和易于维护的企业级应用。
至此,三层架构讲解结束~ 下面开始讲解结合MyBatis和XML映射的DAO 层设计方式
1.MyBatis简介
MyBatis 是一个优秀的持久层框架,它通过消除几乎所有的 JDBC 代码以及手动设置参数和获取结果集的工作,使得开发者能够专注于 SQL 语句本身。MyBatis 允许你以 XML 文件或注解的方式来编写 SQL 语句和映射规则,相当便捷。
网址: MyBatis中文网
2.DAO层职责回顾
忘了没关系~这里稍作总结:DAO层的主要职责是与数据库进行交互,执行 CRUD 操作(创建、读取、更新、删除)。在 MyBatis 中,DAO 层可以很便捷的通过映射文件(XML 文件)或者直接通过注解来定义这些操作。
3. DAO层结合MyBatis和XML映射的设计
(1)项目结构
首先,让我们看一下项目中 DAO 层结合 MyBatis 的典型结构:
src/main/java
│
├── com.example.project.dao
│ └── UserDao.java # 自定义的DAO层接口
│
├── com.example.project.entity
│ └── User.java # 自定义的实体类
│
src/main/resources
│
├── com.example.project.dao # 同名
│ └── UserDao.xml # MyBatis XML 映射文件
│
└── application.properties # Spring Boot 配置文件
(2)创建实体类
实体类代表数据库中的表结构,以User表为例,创建一个对应的User类:
package com.example.project.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String password;
private String email;
}
@Data :自动为类生成所有字段的getter和setter方法,toString()方法,equals()和hashCode()方法,以及一个requiredArgsConstructor(包含final和@NonNull字段的构造函数)。 @AllArgsConstructor :自动生成一个包含所有字段(包括final和非final字段)的构造函数。 @NoArgsConstructor :自动生成一个无参数的默认构造函数。如果类中有final字段,默认生成的构造函数会让这些字段保持未初始化状态(即使用其默认值)。
(3)创建DAO接口
在 MyBatis 中,DAO 接口用于定义数据访问操作。我们可以在接口中定义方法,然后通过 XML 映射文件为每个方法编写对应的 SQL 语句。
package com.example.project.dao;
import com.example.project.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserDao {
User findById(@Param("id") Long id);
List<User> findAll();
void insert(User user);
void update(User user);
void deleteById(@Param("id") Long id);
}
这里使用了@Mapper注解来标识这个接口是 MyBatis 的一个 Mapper(即 DAO)。
@Param注解用于绑定方法参数到 SQL 语句中使用的参数。
(4)编写MyBatis XML映射文件
在这里推荐IntelliJ IDEA里面的一个插件 MyBatisX,可以帮助我们便捷快速的编写MyBatis 的 XML 映射文件~
MyBatis 的 XML 映射文件用于将 DAO 接口中的方法与具体的 SQL 语句进行关联,以下是一个简单的 UserDao.xml 文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.project.dao.UserDao">
<!-- Result Map:用于定义如何将数据库查询结果映射到实体类 -->
<resultMap id="userResultMap" type="com.example.project.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
</resultMap>
<!-- 根据ID查询用户 -->
<select id="findById" resultMap="userResultMap">
SELECT id, username, password, email
FROM users
WHERE id = #{id}
</select>
<!-- 查询所有用户 -->
<select id="findAll" resultMap="userResultMap">
SELECT id, username, password, email
FROM users
</select>
<!-- 添加新用户 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, password, email)
VALUES (#{username}, #{password}, #{email})
</insert>
<!-- 修改用户信息 -->
<update id="update">
UPDATE users
SET username = #{username},
password = #{password},
email = #{email}
WHERE id = #{id}
</update>
<!-- 根据ID删除用户 -->
<delete id="deleteById">
DELETE FROM users
WHERE id = #{id}
</delete>
</mapper>
XML 映射文件解析:
<mapper>:根元素,namespace属性指定了这个映射文件对应的 DAO 接口的全限定名。
<resultMap>:定义了数据库结果如何映射到实体类的字段。id用于标识主键,result用于映射普通字段。
<select>:用于执行查询操作,resultMap属性指定了查询结果的映射规则。
<insert>:用于插入数据。useGeneratedKeys和keyProperty属性用于处理数据库自动生成的主键值。
<update>:用于更新数据。
<delete>:用于删除数据。
(5)Spring配置
在Spring Boot项目中,MyBatis的配置通常在 application.properties 或 application.yml 文件中完成。
以下是一个典型的application.properties文件中的配置:
# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis 配置
mybatis.mapper-locations=classpath:mybatis/*.xml
mybatis.type-aliases-package=com.example.project.entity
配置项解析:
spring.datasource.*:用于配置数据库连接信息。
mybatis.mapper-locations:指定 MyBatis 映射文件的位置。
mybatis.type-aliases-package:指定实体类所在的包,这样在 XML 中可以直接使用类名而不必写全限定名。
包含your_字段的是你自己定义的哈~
以下是一个典型的application.yml文件中的配置:
spring:
# 数据库连接信息配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动程序,这里是MySQL的JDBC驱动
url: jdbc:mysql://localhost:3306/your_database # 数据库连接的URL,指定了数据库服务器地址和数据库名称
username: your_username # 数据库连接的用户名
password: your_password # 数据库连接的密码
# Servlet相关配置
servlet:
multipart:
max-file-size: 10MB # 单个文件上传的最大限制,这里限制为10MB
max-request-size: 100MB # 单次请求中所有文件上传的总大小限制,这里限制为100MB
# MyBatis配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 指定MyBatis的日志实现,输出到标准输出控制台
map-underscore-to-camel-case: true # 启用驼峰命名法转换,例如数据库字段`user_name`将映射为实体类中的 `userName`
mapper-locations: classpath:mappers/*xml # 指定MyBatis的Mapper XML文件的位置,这里指向`mappers`目录下的所有XML文件
type-aliases-package: com.it.mybatis.entity # 指定MyBatis实体类的包名,MyBatis会自动将包中的类映射为数据库表
配置项解析:
数据库连接信息配置:spring.datasource部分定义了如何连接到你的MySQL数据库,包括数据库驱动、连接URL、用户名和密码。
Servlet相关配置:spring.servlet.multipart部分定义了文件上传的大小限制,max-file-size和max-request-size分别控制单个文件和一次请求中所有文件的大小。
MyBatis配置:mybatis.configuration部分配置了MyBatis的日志输出方式和命名策略;mapper-locations指定了Mapper XML文件的存放路径;type-aliases-package则指定了实体类所在的包名,方便MyBatis自动进行映射。
包含your_字段的是你自己定义的哈~
(6)在Service层中使用DAO
在服务层中,可以注入DAO接口来调用相应的数据访问方法:
package com.example.project.service;
import com.example.project.dao.UserDao;
import com.example.project.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional(readOnly = true)
public User getUserById(Long id) {
return userDao.findById(id);
}
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userDao.findAll();
}
@Transactional
public User createUser(User user) {
userDao.insert(user);
return user;
}
@Transactional
public void updateUser(User user) {
userDao.update(user);
}
@Transactional
public void deleteUser(Long id) {
userDao.deleteById(id);
}
}
这里UserService通过@Autowired 注解注入UserDao 来调用数据库操作方法。
@Transactional 注解确保数据操作的原子性,如果出现异常可以进行事务回滚。
(7)总结——结合MyBatis和XML映射的DAO层设计
在实际开发中有以下优势:
1.灵活性:可以编写高度定制化的 SQL 语句,以应对复杂的查询或操作需求。
2.分离关注点:通过 XML 映射文件将 SQL 语句与 Java 代码分离,使代码更清晰、更易维护。
3.可扩展性:易于集成其他数据库操作,如存储过程调用、批量操作等。
建议:
1.在 XML 文件中尽量避免冗长复杂的 SQL,保持 SQL 的可读性。
2.充分利用 MyBatis 的动态 SQL 功能,如 <if>、<choose> 等来实现复杂条件查询。
3.使用 @Mapper 注解和接口定义的方法,可以清晰定义 DAO 层的职责,使得代码结构更加清晰。
如有不足,欢迎指正,一起学习~