目录
一, MyBatis 概述
1.1-介绍 MyBatis 的历史和发展
1.2-MyBatis 的特点和优势
1.3-MyBatis 与 JDBC 的对比
1.4-MyBatis 与其他 ORM 框架的对比
二, 快速入门
2.1-环境搭建
2.2-第一个 MyBatis 应用程序
2.3-配置文件详解 (mybatis-config.xml)
2.4-Mapper 文件详解 (xxxMapper.xml)
三,核心概念
3.1-Session 和 SqlSessionFactory
3.2-映射器 (Mapper) 接口和 XML 映射文件
3.3-SQL 语句的执行流程
3.4-结果映射 (Result Mapping)
四,动态 SQL
4.1-if, choose, when, otherwise
4.2-trim, where, set
4.3-foreach
4.4-bind
五,参数处理
5.1-单个参数
5.2-多个参数
5.3-Map 参数
5.4-POJO 参数
六,类型处理器 (Type Handlers)
6.1-内置类型处理器
6.2-自定义类型处理器
七,高级特性
7.1-关联映射 (一对一、多对一)
7.2-集合映射 (一对多)
7.3-嵌套查询和嵌套结果
7.4-缓存机制 (一级缓存和二级缓存)
7.5-事务管理
八,插件机制
8.1-日志插件
8.2-分页插件
九,集成 Spring
9.1-Spring 中的 MyBatis 配置
9.2-使用 Spring 管理 MyBatis 的 SqlSessionFactory 和 Mapper
十, 最佳实践
10.1-代码组织结构
10.2-异常处理
10.3-测试策略
十一,实战案例
十二,社区和扩展
12.1-MyBatis 生态系统
12.2-第三方工具集成
12.3-社区资源和文档
- 起源:MyBatis 的前身是 iBatis,最初由 Clinton Begin 在2001年创建。
- 发展:2008年左右,iBatis 被捐赠给了 Apache 软件基金会,并更名为 MyBatis。
- 版本演变:MyBatis 3.x 版本引入了大量的改进和新特性,成为目前最广泛使用的版本。
- 社区与生态:MyBatis 拥有活跃的社区和丰富的插件生态系统,包括 MyBatis Generator、MyBatis Plus 等工具。
- 轻量级:MyBatis 相对于其他 ORM 框架来说更为轻量,易于上手且灵活性高。
- SQL 语句编写:允许直接编写 SQL 语句,提供更高的定制能力和优化空间。
- 结果映射:提供强大的结果映射机制,能够灵活地处理复杂的数据结构。
- 动态 SQL:支持动态 SQL 语法,可以根据条件生成不同的 SQL 语句。
- 插件机制:支持插件扩展,可以方便地添加日志记录、分页等功能。
- 与 Spring 的良好集成:能够很好地与 Spring 框架集成,提供依赖注入和事务管理等功能。
封装性:MyBatis 对 JDBC 进行了封装,避免了直接与 JDBC API 交互的繁琐步骤。
资源管理:MyBatis 自动管理 SQL 会话,减少了资源泄漏的风险。
错误处理:MyBatis 提供更友好的异常处理机制,使得错误定位更加容易。
SQL 语句编写:MyBatis 支持直接编写 SQL 语句,而 JDBC 需要程序员自行构建 SQL 语句。
结果映射:MyBatis 提供了自动化的结果映射机制,使得数据转换更为简单。
Hibernate:
- 全自动 vs 半自动:Hibernate 是全自动的 ORM 框架,而 MyBatis 是半自动的。
- 易用性:Hibernate 提供了更多的自动化特性,如懒加载、缓存等。
- 性能:MyBatis 因为更接近 SQL 的控制,所以可能在某些场景下有更好的性能。
JPA (Java Persistence API):
- 标准 vs 实现:JPA 是一种规范,而 Hibernate 是 JPA 的实现之一;MyBatis 则是一种具体的框架。
- API 使用:JPA 使用面向对象的方式,而 MyBatis 更偏向于 SQL 语句的编写。
Spring Data JPA:
- 集成度:Spring Data JPA 与 Spring 集成度更高,提供了更多的便捷方法。
- 灵活性:MyBatis 在编写 SQL 方面提供了更大的灵活性。
1.安装 JDK
- 确保已安装 JDK 1.8 或以上版本。
- 设置环境变量 JAVA_HOME。
2.安装 Maven (可选)
- 如果使用 Maven 管理项目依赖,则需安装 Maven。
- 设置环境变量 M2_HOME 和 MAVEN_HOME。
3.创建 Maven 项目
- 使用 IntelliJ IDEA 或 Eclipse 创建一个新的 Maven 项目。
- 添加 MyBatis 和 MySQL 的依赖到 pom.xml 文件中。
org.mybatis mybatis 3.5.11 mysql mysql-connector-java 8.0.32 org.slf4j slf4j-api 1.7.36 org.slf4j slf4j-simple 1.7.36 4.配置 MySQL 数据库
- 创建数据库和表。
- 配置数据库连接信息。
5.创建实体类
- 根据数据库表结构创建对应的 Java 实体类。
6.创建 MyBatis 配置文件
- 创建 mybatis-config.xml 文件。
7.编写 Mapper 文件
- 创建 UserMapper.xml 文件。
1.创建实体类
- 创建 User.java 实体类。
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; private String name; private String email; }
2.创建 Mapper 接口
- 创建 UserMapper.java 接口。
import org.apache.ibatis.annotations.Select; public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(int id); }
3.创建 Mapper 文件
- 创建 UserMapper.xml 文件。
4.配置文件详解 (mybatis-config.xml)
- 创建 mybatis-config.xml 文件。
5.编写测试类
- 创建 UserTest.java 文件来测试 MyBatis 的基本功能。
import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class UserTest { public static void main(String[] args) throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUserById(1); System.out.println(user); } } }
mybatis-config.xml 是 MyBatis 的核心配置文件,用于配置 MyBatis 的运行环境以及其他各种属性。下面是 mybatis-config.xml 的详细说明:
1. 根元素
属性:无
说明:根元素,包含了所有子元素。
2.
属性:
- resource:指定外部 properties 文件的位置。
- url:指定外部 properties 文件的 URL。
- defaultPrefix:指定默认前缀,用于在 properties 文件中区分默认值。
说明:
- 用于读取外部的 properties 文件,可以用来配置数据库连接信息等。
3.
属性:无
说明:包含一系列的设置项,用于改变 MyBatis 的行为。
4.
属性:无
说明:用于配置别名,可以简化 Java 类型的引用。
5.
属性:无
说明:用于配置自定义的类型处理器,可以处理特定类型的转换。
6.
属性:无
说明:用于配置 MyBatis 创建新实例的方式,默认使用 Java 默认构造器。
7.
属性:无
说明:用于配置 MyBatis 的插件,可以增强或修改 MyBatis 的行为。
8.
属性:无
说明:用于配置不同的环境,如开发、测试、生产等。
:表示一个具体的环境配置。 属性:
id:环境标识符。
子元素:
:配置事务管理器。
属性:
type:事务管理器类型,如 JDBC、MANAGED。
:配置数据源。
属性:
type:数据源类型,如 UNPOOLED、POOLED、JNDI。
子元素:
:配置数据源的属性,如 url、username、password。
9.
属性:无
说明:用于配置数据库 ID 提供者,可以用于区分不同的数据库方言。
10.
属性:无
说明:用于配置 Mapper 接口或 Mapper XML 文件的位置。
:配置单个 Mapper 文件或接口。
属性:
resource:指定 Mapper 文件的资源路径。
url:指定 Mapper 文件的 URL。
class:指定 Mapper 接口的全限定类名。
:批量注册指定包下的所有 Mapper 接口。
属性:
name:指定包名。
xxxMapper.xml 是 MyBatis 的映射文件,用于定义 SQL 语句和结果映射规则。下面是 xxxMapper.xml 的详细说明:
1. 根元素
属性:namespace:指定 Mapper 接口的全限定类名或唯一标识符。
说明:根元素,包含了所有的映射语句。
2.
属性:
- id:映射语句的唯一标识符。
- parameterType:传入参数的类型。
- useGeneratedKeys:是否使用主键自动生成。
- keyProperty:指定自动生成的主键值放入哪个字段。
- keyColumn:指定自动生成的主键值的列名。
说明:定义插入语句。
3.
属性:
- id:映射语句的唯一标识符。
- parameterType:传入参数的类型。
说明:定义更新语句。
4.
属性:
- id:映射语句的唯一标识符。
- parameterType:传入参数的类型。
说明:定义删除语句。
5.
属性:
- id:映射语句的唯一标识符。
- parameterType:传入参数的类型。
- resultType:返回结果的类型。
- resultSetType:结果集类型,如 FORWARD_ONLY、SCROLL_INSENSITIVE、SCROLL_SENSITIVE。
说明:定义查询语句。
6.
属性:
- id:映射语句的唯一标识符。
- type:返回结果的类型。
说明:定义结果映射规则,可以用于复杂的映射逻辑。
7.
属性:id:SQL 片段的唯一标识符。
说明:定义可重用的 SQL 片段。
8.
属性:refid:引用
元素的 ID。
说明:引用元素定义的 SQL 片段。
9. 动态 SQL
元素:
, , , , , , ,
说明:用于构建动态 SQL 语句。
INSERT INTO users (name, email) VALUES (#{name}, #{email})
UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
DELETE FROM users WHERE id = #{id}
Session 和 SqlSessionFactory
SqlSessionFactory:
SqlSessionFactory 是 MyBatis 中最重要的对象之一,它是线程安全的,可以在应用程序的生命周期内存在一个实例。
它的主要作用是创建 SqlSession 对象。
通常情况下,SqlSessionFactory 会在应用程序启动时创建一次,并在整个应用的生命周期中复用。
SqlSession:
SqlSession 是 MyBatis 的主要 API,它代表了一次数据库会话。
每当需要执行 SQL 语句时,都需要从 SqlSessionFactory 中获取一个 SqlSession 实例。
SqlSession 是线程不安全的,因此每次使用后应该关闭它。
SqlSession 提供了执行 SQL 语句的方法,包括 insert、update、delete 和 select。
Mapper 接口:
Mapper 接口是 Java 接口,它定义了 SQL 语句的抽象方法。
这些方法的名称和参数类型被 MyBatis 用来定位相应的 SQL 语句。
当调用 Mapper 接口的方法时,MyBatis 会执行相应的 SQL 语句并返回结果。
XML 映射文件:
XML 映射文件包含了具体的 SQL 语句和结果映射规则。
映射文件中的每个标签都有一个 namespace 属性,该属性的值通常是 Mapper 接口的全限定名。
映射文件中的
1.获取 SqlSessionFactory:
通过 SqlSessionFactoryBuilder 构建一个 SqlSessionFactory 实例。
2.打开 SqlSession:
通过 SqlSessionFactory.openSession() 方法获取一个 SqlSession 实例。
3.获取 Mapper:
通过 SqlSession.getMapper() 方法获取一个 Mapper 接口的实现。
4.执行 SQL 语句:
调用 Mapper 接口中定义的方法来执行 SQL 语句。
MyBatis 会根据方法签名查找对应的 SQL 语句并执行。
5.处理结果:
如果是查询语句,MyBatis 会根据
如果是 DML 语句(如 insert、update、delete),则返回影响的行数。
6.提交或回滚:
如果需要,调用 SqlSession.commit() 提交事务。
如果出现错误,调用 SqlSession.rollback() 回滚事务。
7.关闭 SqlSession:
最后,调用 SqlSession.close() 关闭会话。
概述:
结果映射是 MyBatis 的核心特性之一,它允许开发者定义如何将 SQL 查询结果映射到 Java 对象。
通过
基本映射:
- 在
中,使用 和 标签来定义列到 Java 属性的映射。 通常用于主键字段,而 用于其他字段。 - 可以指定 column 和 property 属性来关联数据库列名和 Java 属性名。
高级映射:
- 对于复杂的一对多或一对多关系,可以使用
和 标签。 用于一对一关系,而 用于一对多关系。 - 这些标签支持嵌套查询和嵌套结果映射,从而可以处理复杂的对象图。
if:
if:用于根据条件判断是否添加某个 SQL 子句。
属性:test: 表达式,如果为 true,则包含此元素内的 SQL 语句;否则忽略。
示例:
AND name = #{name}
choose, when, otherwise:
choose, when, otherwise:
类似于 Java 中的 switch-case 语句,用于多个条件的选择。
choose 开始选择结构,when 定义一个条件,otherwise 定义默认情况。
示例:
AND name = #{name} AND age = #{age} ORDER BY id DESC
trim, where, set:
trim:用于去除多余的前缀或后缀,例如去除多余的 AND 或 OR。
属性:
- prefix: 前缀字符串。
- suffix: 后缀字符串。
- prefixOverrides: 指定需要去除的前缀字符串。
- suffixOverrides: 指定需要去除的后缀字符串。
示例:
name = #{name} AND age = #{age} AND
where:
where:
用于自动处理 WHERE 子句的添加和多余的 AND 或 OR 的去除。
示例:
name = #{name} AND age = #{age}
set:
set:
用于自动处理 SET 子句的添加和多余的逗号的去除。
示例:
name = #{name}, age = #{age},
foreach:
foreach:
用于遍历集合或数组,并生成多个 SQL 子句。
属性:
- collection: 集合或数组的名称。
- item: 遍历时使用的变量名称。
- index: 集合索引的变量名称。
- open: 开始符号。
- close: 结束符号。
- separator: 分隔符。
示例:
#{id}
bind:
bind:
用于绑定表达式的值到一个变量。
属性:
- name: 绑定变量的名称。
- value: 绑定的表达式。
示例:
直接使用:
当只有一个参数时,可以直接使用 #{} 占位符。
示例:
使用 @Param 注解:
即使只有一个参数,也可以使用 @Param 注解来明确参数名称。
示例:
使用 @Param 注解:
当有多个参数时,可以使用 @Param 注解来指定每个参数的名称。
示例:JAVA:
List
findUsers(@Param("name") String name, @Param("age") Integer age); XML:
使用 Map 参数:
也可以直接传递一个 Map 对象作为参数。
示例:JAVA:
List
findUsers(Map params); XML:
直接传递 Map:
当需要传递多个参数时,可以使用 Map 对象。
示例:JAVA:
List
findUsersByParams(Map params); XML:
使用 POJO 对象:
当参数是一个 Java 对象时,可以使用该对象的属性作为 SQL 语句中的参数。
示例:public class UserSearchCriteria { private String name; private Integer age; // Getters and Setters } List
findUsers(UserSearchCriteria criteria); XML:
MyBatis 提供了一系列内置的类型处理器,用于处理常见的 Java 类型和 JDBC 类型之间的转换。这些内置类型处理器包括但不限于:
基本数据类型:
- BooleanTypeHandler
- ByteTypeHandler
- ShortTypeHandler
- IntegerTypeHandler
- LongTypeHandler
- FloatTypeHandler
- DoubleTypeHandler
- BigDecimalTypeHandler
- StringTypeHandler
- NStringTypeHandler
- DateTypeHandler
- TimestampTypeHandler
- BlobTypeHandler
- ClobTypeHandler
其他类型:
- EnumTypeHandler
- EnumOrdinalTypeHandler
- LocalDateTimeTypeHandler
- LocalDateTypeHandler
- LocalTimeTypeHandler
这些内置类型处理器通常不需要额外配置,因为 MyBatis 会自动识别并使用它们。
对于一些特殊的数据类型,如自定义的 Java 类或枚举类型,可能需要自定义类型处理器来实现转换逻辑。
创建自定义类型处理器:
创建一个类实现 org.apache.ibatis.type.TypeHandler 接口。
实现 setParameter 和 getResult 方法。
可以使用 @MappedJdbcTypes 和 @MappedTypes 注解来指定处理器支持的 JDBC 类型和 Java 类型。
注册自定义类型处理器:
在 MyBatis 的配置文件中注册类型处理器。
或者使用 typeHandlers 元素来注册自定义类型处理器。
在关联映射中,你需要处理两个实体之间的关系,例如一对多或多对一的关系。MyBatis 支持通过
和 标签来处理这些关系。
一对一关联映射:
使用标签来处理两个实体之间的一对一关系。
示例:
多对一关联映射:
使用标签来处理两个实体之间的多对一关系。
示例:
当需要处理一对多的关系时,可以使用
标签来处理。
一对多关联映射:
使用标签来处理两个实体之间的一对多关系。
示例:
在某些情况下,你可能希望先执行一次查询来获取主实体,然后再基于主实体的结果执行额外的查询来获取关联实体。这可以通过嵌套查询和嵌套结果来实现。
嵌套查询:
使用或 标签中的 select 属性来指定另一个查询 ID。
示例:
嵌套结果:
使用
或 标签中的 column 属性来指定关联查询所需的列。
示例:
MyBatis 提供了一级缓存和二级缓存机制来提高应用程序的性能。
一级缓存:
默认开启,基于 PerSqlSession 的缓存,即同一个 SqlSession 中的两次相同的查询会从缓存中获取数据。
示例:try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper userMapper = session.getMapper(UserMapper.class); User user = userMapper.findUserById(1); // 第一次查询 User user2 = userMapper.findUserById(1); // 从缓存中获取 }
二级缓存:
需要手动开启,并且默认关闭。
基于 PerNamespace 的缓存,即同一个命名空间下的所有 SQL 语句共享一个缓存。
示例:
MyBatis 支持多种事务管理方式,包括通过 JDBC 连接管理事务或集成 Spring 框架进行事务管理。
JDBC 事务管理:
在 SqlSession 中管理事务。
示例:try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper userMapper = session.getMapper(UserMapper.class); userMapper.insertUser(new User("Alice", 30)); session.commit(); // 提交事务 } catch (Exception e) { session.rollback(); // 回滚事务 }
Spring 事务管理:
通过 Spring 的 AOP 功能管理事务。
示例:@Transactional public void createUser(User user) { userMapper.insertUser(user); }
日志插件可以帮助你记录 SQL 语句和参数,这对于调试和性能分析非常有用。
实现:
创建一个实现了 org.apache.ibatis.plugin.Interceptor 接口的类。
实现 intercept 方法来拦截目标对象的方法调用。
使用 @Intercepts 注解来指定要拦截的目标对象和方法。
示例:import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.util.Properties; @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class LoggingInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]); System.out.println("Executing SQL: " + boundSql.getSql()); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
注册:
在 MyBatis 的配置文件中注册插件。
示例:
分页插件可以自动修改 SQL 语句来支持分页查询。
实现:
创建一个实现了 org.apache.ibatis.plugin.Interceptor 接口的类。
实现 intercept 方法来修改 SQL 语句以支持分页。
使用 @Intercepts 注解来指定要拦截的目标对象和方法。
示例:import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.sql.Connection; import java.util.Properties; @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class PaginationInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; RowBounds rowBounds = (RowBounds) invocation.getArgs()[2]; Connection connection = (Connection) invocation.getArgs()[3]; BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]); String originalSql = boundSql.getSql(); // Modify the SQL for pagination String paginatedSql = modifySqlForPagination(originalSql, rowBounds); // Update the BoundSql with the modified SQL BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), paginatedSql, boundSql.getParameterMappings(), boundSql.getParameterObject()); mappedStatement.setBoundSql(newBoundSql); return invocation.proceed(); } private String modifySqlForPagination(String originalSql, RowBounds rowBounds) { // Implement pagination logic based on the database dialect // Example for MySQL: int offset = rowBounds.getOffset(); int limit = rowBounds.getLimit(); return originalSql + " LIMIT " + offset + ", " + limit; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
注册:
在 MyBatis 的配置文件中注册插件。
示例:
8.3-性能监控插件
性能监控插件可以用来记录 SQL 执行的时间和其他性能指标。
实现:
创建一个实现了 org.apache.ibatis.plugin.Interceptor 接口的类。
实现 intercept 方法来记录 SQL 执行的时间。
使用 @Intercepts 注解来指定要拦截的目标对象和方法。
示例:import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.util.Properties; @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class PerformanceMonitorInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); Object result = invocation.proceed(); long end = System.currentTimeMillis(); long executionTime = end - start; MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]); System.out.println("Execution time: " + executionTime + " ms for SQL: " + boundSql.getSql()); return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
注册:
在 MyBatis 的配置文件中注册插件。
示例:
解释
LoggingInterceptor:
实现了 Interceptor 接口。
使用 @Intercepts 注解指定了要拦截的 Executor 类的 update 和 query 方法。
在 intercept 方法中记录了 SQL 语句。
PaginationInterceptor:
实现了 Interceptor 接口。
使用 @Intercepts 注解指定了要拦截的 Executor 类的 query 方法。
在 intercept 方法中修改了 SQL 语句以支持分页。
修改了 BoundSql 对象以反映新的 SQL 语句。
PerformanceMonitorInterceptor:
实现了 Interceptor 接口。
使用 @Intercepts 注解指定了要拦截的 Executor 类的 update 和 query 方法。
在 intercept 方法中记录了 SQL 执行的时间。
首先确保你的项目包含了 Spring 和 MyBatis 相关的依赖。如果你使用 Maven,可以在 pom.xml 文件中添加如下依赖:
org.springframework spring-context ${spring.version} org.springframework spring-jdbc ${spring.version} org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 mysql mysql-connector-java ${mysql.driver.version}
在 Spring 配置文件(如 applicationContext.xml 或使用 Java Config 类)中配置数据源和 SqlSessionFactory。
XML 配置示例:
Java Config 示例:
@Configuration public class DatabaseConfig { @Value("${spring.datasource.url}") private String dbUrl; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl(dbUrl); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml")); return factoryBean.getObject(); } }
配置 MapperScannerConfigurer
为了自动扫描和注册 Mapper 接口,你需要配置 MapperScannerConfigurer。这一步可以通过 XML 或 Java Config 完成。
XML 配置示例:
Java Config 示例:
@Configuration public class MyBatisConfig { @Autowired private SqlSessionFactory sqlSessionFactory; @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer configurer = new MapperScannerConfigurer(); configurer.setBasePackage("com.yourcompany.yourproject.mapper"); configurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); return configurer; } }
使用 Mapper 接口
在你的服务类中,直接注入 Mapper 接口,Spring 会自动创建代理对象并管理它的生命周期。
@Service public class UserService { @Autowired private UserMapper userMapper; public List
getAllUsers() { return userMapper.selectAll(); } }
1.模块化设计:
- 将应用程序划分为多个模块或包,例如 dao, service, controller 等。
- 每个模块负责特定的功能,如数据访问、业务逻辑处理、用户交互等。
2.DAO 层设计:
- DAO (Data Access Object) 层用于封装对数据库的操作。
- 每个实体类都应该有一个对应的 DAO 接口和实现类。
- DAO 接口定义了与数据库交互的方法,实现类则使用 MyBatis 来执行具体的 SQL 操作。
3.Service 层设计:
- Service 层用于处理业务逻辑。
- 服务层调用 DAO 层提供的方法,并进行必要的业务逻辑处理。
- 通常情况下,服务层不直接操作数据库。
4.Controller 层设计:
- Controller 层负责接收 HTTP 请求并调用服务层进行处理。
- 控制器层还负责将处理结果返回给客户端。
5.资源分离:
- 将 MyBatis 的映射文件(XML 文件)与 Java 代码分离。
- 映射文件应该放在专门的目录下,如 src/main/resources/mapper。
- 映射文件中定义 SQL 查询语句和结果映射规则。
6.配置文件管理:
- 使用单独的配置文件来管理 MyBatis 的配置信息,如数据库连接信息、插件配置等。
- 配置文件应放置在 src/main/resources 目录下。
7.实体类设计:
- 设计实体类时应遵循 POJO (Plain Old Java Object) 原则。
- 实体类应仅包含属性和基本的 getter/setter 方法。
- 避免在实体类中添加复杂的业务逻辑。
统一异常处理:
- 定义一套统一的异常处理机制,确保所有异常都能被正确捕获和处理。
- 可以创建自定义异常类来区分不同类型的错误。
异常层次结构:
- 根据异常类型定义不同的异常类,例如 DataAccessException、BusinessException 等。
- 通过继承 RuntimeException 或 Exception 来创建自定义异常类。
异常抛出:
- 在 DAO 层抛出 DataAccessException 类型的异常,表示数据访问过程中出现的问题。
- 在 Service 层抛出 BusinessException 类型的异常,表示业务逻辑处理中的问题。
异常捕获:
- 在 Controller 层捕获异常,并根据异常类型返回合适的响应。
- 使用全局异常处理器来处理未被捕获的异常。
异常日志记录:
- 在捕获异常时记录详细的错误信息,包括堆栈跟踪。
- 使用日志框架(如 Logback、Log4j 等)记录异常信息
单元测试:
- 编写针对 DAO 层和服务层的单元测试。
- 使用 JUnit 或 TestNG 框架编写测试用例。
- 对于 DAO 层,可以使用内存数据库(如 H2)来进行测试。
集成测试:
- 编写针对整个系统或子系统的集成测试。
- 使用真实的数据库环境来测试数据访问层和业务逻辑层之间的交互。
- 可以使用 Spring Boot 的 @SpringBootTest 注解来启动完整的应用上下文进行测试。
端到端测试:
- 对整个应用流程进行端到端的测试,确保各个组件之间能够正常协作。
- 使用 Selenium 或 RestAssured 等工具进行前端和后端的交互测试。
性能测试:
- 使用 JMeter 或 Gatling 等工具进行性能测试,确保应用在高并发下的稳定性和性能。
- 测试数据库查询性能,优化 SQL 语句和索引。
代码覆盖率:
- 使用 JaCoCo 或 Cobertura 等工具来检查测试代码的覆盖率。
- 努力提高代码覆盖率,确保关键路径和边界条件都得到了测试。
完整的应用示例
假设我们需要构建一个简单的博客系统,其中包含文章(Article)和标签(Tag)两个实体。我们将实现以下功能:
步骤 1: 创建项目结构
项目结构:
src/main/java: 存放 Java 源代码。
src/main/resources: 存放配置文件和映射文件。
模块划分:
com.example: 包含所有源代码。
com.example.model: 实体类。
com.example.mapper: 映射接口。
com.example.service: 服务层接口和实现。
com.example.controller: 控制器层。
com.example.config: 配置类。
步骤 2: 创建实体类
Article.java:
package com.example.model;
public class Article {
private Integer id;
private String title;
private String content;
private Integer authorId;
// Getters and setters
}
Tag.java:
package com.example.model;
public class Tag {
private Integer id;
private String name;
// Getters and setters
}
步骤 3: 创建映射接口
ArticleMapper.java:
package com.example.mapper;
import com.example.model.Article;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface ArticleMapper {
@Select("SELECT * FROM articles WHERE id = #{id}")
Article findById(Integer id);
@Insert("INSERT INTO articles(title, content, author_id) VALUES(#{title}, #{content}, #{authorId})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void add(Article article);
@Update("UPDATE articles SET title = #{title}, content = #{content} WHERE id = #{id}")
void update(Article article);
@Delete("DELETE FROM articles WHERE id = #{id}")
void deleteById(Integer id);
@Select("SELECT * FROM articles")
List findAll();
}
TagMapper.java:
package com.example.mapper;
import com.example.model.Tag;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface TagMapper {
@Select("SELECT * FROM tags WHERE id = #{id}")
Tag findById(Integer id);
@Insert("INSERT INTO tags(name) VALUES(#{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void add(Tag tag);
@Update("UPDATE tags SET name = #{name} WHERE id = #{id}")
void update(Tag tag);
@Delete("DELETE FROM tags WHERE id = #{id}")
void deleteById(Integer id);
@Select("SELECT * FROM tags")
List findAll();
}
步骤 4: 创建服务层
ArticleService.java:
package com.example.service;
import com.example.mapper.ArticleMapper;
import com.example.model.Article;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ArticleService {
private final ArticleMapper articleMapper;
@Autowired
public ArticleService(ArticleMapper articleMapper) {
this.articleMapper = articleMapper;
}
public Article findById(Integer id) {
return articleMapper.findById(id);
}
public void add(Article article) {
articleMapper.add(article);
}
public void update(Article article) {
articleMapper.update(article);
}
public void deleteById(Integer id) {
articleMapper.deleteById(id);
}
public List findAll() {
return articleMapper.findAll();
}
}
TagService.java:
package com.example.service;
import com.example.mapper.TagMapper;
import com.example.model.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TagService {
private final TagMapper tagMapper;
@Autowired
public TagService(TagMapper tagMapper) {
this.tagMapper = tagMapper;
}
public Tag findById(Integer id) {
return tagMapper.findById(id);
}
public void add(Tag tag) {
tagMapper.add(tag);
}
public void update(Tag tag) {
tagMapper.update(tag);
}
public void deleteById(Integer id) {
tagMapper.deleteById(id);
}
public List findAll() {
return tagMapper.findAll();
}
}
步骤 5: 创建控制器层
ArticleController.java:
package com.example.controller;
import com.example.model.Article;
import com.example.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/articles")
public class ArticleController {
private final ArticleService articleService;
@Autowired
public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
@GetMapping("/{id}")
public Article findById(@PathVariable Integer id) {
return articleService.findById(id);
}
@PostMapping
public void add(@RequestBody Article article) {
articleService.add(article);
}
@PutMapping("/{id}")
public void update(@PathVariable Integer id, @RequestBody Article article) {
article.setId(id);
articleService.update(article);
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable Integer id) {
articleService.deleteById(id);
}
@GetMapping
public List findAll() {
return articleService.findAll();
}
}
TagController.java:
package com.example.controller;
import com.example.model.Tag;
import com.example.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/tags")
public class TagController {
private final TagService tagService;
@Autowired
public TagController(TagService tagService) {
this.tagService = tagService;
}
@GetMapping("/{id}")
public Tag findById(@PathVariable Integer id) {
return tagService.findById(id);
}
@PostMapping
public void add(@RequestBody Tag tag) {
tagService.add(tag);
}
@PutMapping("/{id}")
public void update(@PathVariable Integer id, @RequestBody Tag tag) {
tag.setId(id);
tagService.update(tag);
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable Integer id) {
tagService.deleteById(id);
}
@GetMapping
public List findAll() {
return tagService.findAll();
}
}
步骤 6: 配置 MyBatis
application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.example.model
mybatis.mapper-locations=classpath:mapper/*.xml
MyBatisConfig.java:
package com.example.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.example.model");
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return factoryBean.getObject();
}
}
步骤 7: 创建映射文件
ArticleMapper.xml: