第九章:数据库编程 - ORM框架(下)
在上一部分中,我们学习了ORM框架的基础知识和Hibernate框架。在这一部分中,我们将继续学习其他流行的ORM框架,包括MyBatis和Spring Data JPA。
1. MyBatis框架
1.1 MyBatis简介
MyBatis(前身是iBatis)是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
生活中的例子:
如果说Hibernate是一位不允许你干预的全能翻译官(它会自动将对象转换为SQL),那么MyBatis则像是一位允许你提供翻译指南的翻译官。你可以告诉MyBatis:"当我说'找出所有年龄大于18岁的用户'时,请使用这个特定的SQL语句。"这样,你既享受了ORM的便利,又保留了对SQL的控制权。
1.2 MyBatis的核心组件
1.2.1 SqlSessionFactory
SqlSessionFactory是MyBatis的核心组件,它负责创建SqlSession对象。通常,应用程序中只需要一个SqlSessionFactory实例。
java
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1.2.2 SqlSession
SqlSession是MyBatis的主要接口,通过它可以执行SQL命令、获取映射器和管理事务。
java
try (SqlSession session = sqlSessionFactory.openSession()) {
// 执行SQL操作
User user = session.selectOne("com.example.UserMapper.selectUser", 1);
// 提交事务
session.commit();
}
1.2.3 Mapper接口
Mapper接口是MyBatis中定义SQL操作的接口,通过它可以将SQL操作与Java方法关联起来。
java
public interface UserMapper {
User selectUser(int id);
List<User> selectAllUsers();
void insertUser(User user);
void updateUser(User user);
void deleteUser(int id);
}
1.3 MyBatis配置
1.3.1 核心配置文件
MyBatis的核心配置文件包含了影响MyBatis行为的设置和属性。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/UserMapper.xml"/>
</mappers>
</configuration>
1.3.2 映射文件
映射文件包含了SQL语句和结果映射的定义。
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.UserMapper">
<select id="selectUser" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
<select id="selectAllUsers" resultType="com.example.User">
SELECT * FROM users
</select>
<insert id="insertUser" parameterType="com.example.User">
INSERT INTO users (username, email) VALUES (#{username}, #{email})
</insert>
<update id="updateUser" parameterType="com.example.User">
UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}
</update>
<delete id="deleteUser" parameterType="int">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
1.4 MyBatis的查询方式
1.4.1 基于XML的查询
java
try (SqlSession session = sqlSessionFactory.openSession()) {
// 使用命名空间和SQL ID执行查询
User user = session.selectOne("com.example.UserMapper.selectUser", 1);
}
1.4.2 基于接口的查询
java
try (SqlSession session = sqlSessionFactory.openSession()) {
// 获取Mapper接口
UserMapper userMapper = session.getMapper(UserMapper.class);
// 通过接口方法执行查询
User user = userMapper.selectUser(1);
}
1.5 MyBatis的动态SQL
MyBatis提供了强大的动态SQL功能,可以根据条件动态生成SQL语句。
xml
<select id="findUsers" resultType="com.example.User">
SELECT * FROM users
<where>
<if test="username != null">
AND username LIKE #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
</select>
1.6 MyBatis的缓存机制
MyBatis提供了两级缓存机制:一级缓存(SqlSession级别)和二级缓存(命名空间级别)。
1.6.1 一级缓存
一级缓存是SqlSession级别的缓存,默认开启。当我们使用同一个SqlSession多次执行相同的SQL语句时,只有第一次会访问数据库,后续的查询会直接从缓存中获取结果。
java
try (SqlSession session = sqlSessionFactory.openSession()) {
// 第一次查询,会访问数据库
User user1 = session.selectOne("com.example.UserMapper.selectUser", 1);
// 第二次查询,直接从一级缓存获取,不会访问数据库
User user2 = session.selectOne("com.example.UserMapper.selectUser", 1);
}
1.6.2 二级缓存
二级缓存是命名空间级别的缓存,可以在多个SqlSession之间共享数据。需要在映射文件中显式配置才能启用。
xml
<!-- 在映射文件中启用二级缓存 -->
<cache/>
java
// 实体类需要实现Serializable接口
public class User implements Serializable {
// ...
}
1.7 MyBatis与Spring的集成
MyBatis可以与Spring框架无缝集成,简化配置和使用。
xml
<!-- 在Spring配置文件中配置MyBatis -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:com/example/*Mapper.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example" />
</bean>
1.8 MyBatis最佳实践
- 使用接口绑定:使用Mapper接口而非字符串命名空间和SQL ID,提高类型安全性
- 合理使用动态SQL:利用动态SQL特性,减少重复代码
- 使用ResultMap:对于复杂查询结果,使用ResultMap进行映射
- 批量操作:对于大批量数据操作,使用批量插入、更新或删除
- 合理配置缓存:根据应用场景合理配置和使用缓存
2. Spring Data JPA
2.1 Spring Data JPA简介
Spring Data JPA是Spring Data家族的一部分,它提供了一种简化JPA(Java Persistence API)开发的方式。Spring Data JPA通过减少数据访问层的代码量,使开发者能够更加专注于业务逻辑。
生活中的例子:
想象一下,如果Hibernate是一位需要你提供详细指令的翻译官,那么Spring Data JPA就像是一位能够理解你意图的智能助手。你只需告诉它:"我想要根据用户名查找用户",它就能自动理解并执行这个操作,而不需要你详细说明如何执行。
2.2 Spring Data JPA的核心组件
2.2.1 Repository接口
Repository是Spring Data JPA的核心接口,它提供了一组通用的数据访问方法。
java
public interface UserRepository extends JpaRepository<User, Long> {
// 无需实现,Spring Data JPA会自动提供基本的CRUD操作
}
2.2.2 实体类
实体类是与数据库表对应的Java类,使用JPA注解进行映射。
java
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// 构造函数、getter和setter方法
}
2.3 Spring Data JPA配置
2.3.1 Maven依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.3.2 Spring Boot配置
properties
# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
2.4 Spring Data JPA的查询方法
2.4.1 方法名查询
Spring Data JPA支持通过方法名自动生成查询语句。
java
public interface UserRepository extends JpaRepository<User, Long> {
// 根据用户名查找用户
User findByUsername(String username);
// 根据邮箱查找用户
User findByEmail(String email);
// 查找用户名包含指定字符串的用户
List<User> findByUsernameContaining(String username);
// 查找用户名以指定字符串开头且邮箱以指定字符串结尾的用户
List<User> findByUsernameStartingWithAndEmailEndingWith(String usernamePrefix, String emailSuffix);
}
2.4.2 @Query注解
对于复杂查询,可以使用@Query注解自定义查询语句。
java
public interface UserRepository extends JpaRepository<User, Long> {
// 使用JPQL查询
@Query("SELECT u FROM User u WHERE u.username = :username AND u.email = :email")
User findByUsernameAndEmail(@Param("username") String username, @Param("email") String email);
// 使用原生SQL查询
@Query(value = "SELECT * FROM users WHERE username = ?1", nativeQuery = true)
User findByUsernameNative(String username);
// 更新查询
@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
int updateEmail(@Param("id") Long id, @Param("email") String email);
}
2.5 Spring Data JPA的分页和排序
Spring Data JPA提供了简单的分页和排序支持。
java
// 在Repository接口中定义分页查询方法
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByUsernameContaining(String username, Pageable pageable);
}
// 在Service中使用分页查询
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Page<User> findUsersByUsername(String username, int page, int size) {
// 创建分页请求
PageRequest pageRequest = PageRequest.of(page, size, Sort.by("username").ascending());
// 执行分页查询
return userRepository.findByUsernameContaining(username, pageRequest);
}
}
2.6 Spring Data JPA的关联关系
2.6.1 一对一关系
java
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "address_id")
private Address address;
// 构造函数、getter和setter方法
}
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String city;
// 构造函数、getter和setter方法
}
2.6.2 一对多关系
java
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
private List<Employee> employees = new ArrayList<>();
// 构造函数、getter和setter方法
}
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
// 构造函数、getter和setter方法
}
2.6.3 多对多关系
java
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
// 构造函数、getter和setter方法
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
// 构造函数、getter和setter方法
}
2.7 Spring Data JPA的审计功能
Spring Data JPA提供了审计功能,可以自动记录实体的创建时间、最后修改时间、创建者和最后修改者。
java
// 启用JPA审计
@Configuration
@EnableJpaAuditing
public class JpaConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("当前用户");
}
}
// 在实体类中使用审计注解
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
private String createdBy;
@LastModifiedBy
private String updatedBy;
// 构造函数、getter和setter方法
}
2.8 Spring Data JPA最佳实践
- 使用方法名查询:对于简单查询,使用方法名查询可以减少代码量
- 使用@Query注解:对于复杂查询,使用@Query注解自定义查询语句
- 合理使用关联关系:根据业务需求合理设计实体之间的关联关系
- 使用分页和排序:对于大量数据,使用分页和排序提高性能
- 启用审计功能:使用审计功能自动记录实体的变更历史
3. ORM框架对比
3.1 Hibernate vs MyBatis
特性 | Hibernate | MyBatis |
---|---|---|
学习曲线 | 较陡峭 | 较平缓 |
SQL控制 | 自动生成,控制较少 | 手动编写,控制较多 |
性能 | 对于简单查询较好 | 对于复杂查询较好 |
灵活性 | 较低 | 较高 |
开发效率 | 较高 | 中等 |
适用场景 | 简单CRUD操作 | 复杂查询和存储过程 |
3.2 Spring Data JPA vs Hibernate
特性 | Spring Data JPA | Hibernate |
---|---|---|
学习曲线 | 较平缓 | 较陡峭 |
代码量 | 较少 | 较多 |
功能丰富度 | 中等 | 高 |
灵活性 | 中等 | 高 |
开发效率 | 很高 | 高 |
适用场景 | 标准CRUD操作 | 复杂映射和查询 |
3.3 如何选择ORM框架
选择合适的ORM框架应考虑以下因素:
- 项目复杂度:对于简单项目,Spring Data JPA可能是最佳选择;对于复杂项目,Hibernate或MyBatis可能更合适
- 团队经验:考虑团队成员对各框架的熟悉程度
- 性能要求:对于性能要求高的场景,MyBatis可能更合适
- SQL控制:如果需要精确控制SQL,MyBatis是更好的选择
- 开发效率:如果追求开发效率,Spring Data JPA是不错的选择
总结
在本章中,我们学习了三种主流的ORM框架:Hibernate、MyBatis和Spring Data JPA。这些框架各有特点,适用于不同的场景。
Hibernate是一个功能强大的ORM框架,它通过对象关系映射技术,使开发者能够使用面向对象的方式操作数据库,但学习曲线较陡峭。
MyBatis是一个灵活的持久层框架,它允许开发者精确控制SQL语句,适合于复杂查询和对性能要求较高的场景。
Spring Data JPA是一个简化JPA开发的框架,它通过约定大于配置的方式,大大减少了数据访问层的代码量,提高了开发效率。
在实际项目中,应根据项目需求和团队情况选择合适的ORM框架。有时候,甚至可以在同一个项目中混合使用多种框架,以发挥各自的优势。