简介
JPA
JPA是Java Persistence API,即Java持久化存储API,更多地是代表一种规范。
规范有以下特点
- 易用
- 代码可读性好
- CRUD等代码重用性好,减少工作量
- 通过注解等方式实现实体到数据库表格的映射
- 包括三部分:Entity实体、EntityManager实体管理类、Query查询三部分
MyBatis
MyBatis相对JPA来说,是将编辑SQL语句的部分暴露在外部,比JPA要更麻烦,但是自由度更高,支持高度定制,对于复杂需求和高效率需求等特殊需求的SQL语句更为友好。
应为JPA为了便利性将这部分代码进行了封装,使用方便的代价便是定制化度的下降。
Spring Data JPA使用
步骤
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456
创建实体类
@Entity
@Table(name = "user") // 标识此实体类对应的表格名称是user,如果不指定,默认的表格为类名由转化为"_"方式命名的表格 public class User { @Id // @Id是表示字段id对应表格主键 @GeneratedValue(strategy = GenerationType.IDENTITY) // 表示这是一个自增长的主键,通常与@Id一起使用 private Long id;
@Cloumn(name = "name",length=20,nullable=false) // 对应字段和变量,默认将则对应驼峰规则转化为"_"规则后的字段 private String name; }
创建仓库管理接口
也就是具备查询语句功能的中间接口
@Repository // 标注这是一个仓库管理类,纳入spring管理 public interface UserRepository extends JpaRepository<User, Interger> { // 需要继承JpaRepository,其中User对应实体,Interger对应主键类型 User findOne(int id); }
使用仓库管理接口
@Service public class UserService { @Autowired private UserRepository userRepository; public User findOne(int id){ return userRepository.findOne(id); } public List<User> findAll() { return userRepository.findAll(); } public User save(User user) { return userRepository.save(user); } public void deleteById(int id) { userRepository.delete(id); } }
JpaRepository接口默认方法介绍
|
方法名
|
描述
|
|---|---|
|
T save(T entity)
|
保存实体对象
|
|
Iterable saveAll(Iterable entities)
|
批量保存实体对象
|
|
Optional findById(ID id)
|
根据主键获取实体对象
|
|
boolean existsById(ID id)
|
判断是否存在特定主键的实体对象
|
|
Iterable findAll()
|
获取所有实体对象
|
|
Iterable findAllById(Iterable ids)
|
根据主键批量获取实体对象
|
|
long count()
|
获取实体对象的数量
|
|
void deleteById(ID id)
|
根据主键删除实体对象
|
|
void delete(T entity)
|
删除实体对象
|
|
void deleteAll(Iterable<? extends T> entities)
|
批量删除实体对象
|
继承JpaRepository后自定义接口
简单接口自定义无需注解
模块可以根据方法名推断sql语句,但是要遵守规则的方法名
| Keyword | Sample | JPQL snippet |
|---|---|---|
| And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
| Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
| Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
| Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
| LessThan | findByAgeLessThan | … where x.age < ?1 |
| LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
| GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
| GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
| After | findByStartDateAfter | … where x.startDate > ?1 |
| Before | findByStartDateBefore | … where x.startDate < ?1 |
| IsNull | findByAgeIsNull | … where x.age is null |
| IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
| Like | findByFirstnameLike | … where x.firstname like ?1 |
| NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
| StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
| EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
| Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
| OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
| Not | findByLastnameNot | … where x.lastname <> ?1 |
| In | findByAgeIn(Collection ages) | … where x.age in ?1 |
| NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
| TRUE | findByActiveTrue() | … where x.active = true |
| FALSE | findByActiveFalse() | … where x.active = false |
| IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
例如:findBy标识根据xx查询,deleteBy标识根据xx删除
public interface UserRepository extends Repository<User, Long> { // 根据用户名查询用户 User findByUserName(String userName); // 根据年龄查询用户列表 List<User> findByAge(Integer age);
// 根据实体的某些条件进行查询,如果需要按照用户的某些属性进行查询,则将筛选的属性值初始化,其他属性值则不初始化
List<User> findUserList(User user);
// 根据用户名和密码查询用户 User findByUserNameAndPassword(String userName, String password); // 根据主键和用户名删除用户 void deleteByIdAndUserName(Long id, String userName); }
复杂自定义接口
可以使用@param指向参数,也可以使用占位符定位参数
public interface UserRepository extends Repository<User, Long> { // 根据用户名和密码查询用户 @Query(value = "SELECT * FROM user WHERE user_name = ?1 AND password = ?2", nativeQuery = true) User findByUserNameAndPassword1(String userName, String password); // 根据用户名和密码查询用户 @Query("SELECT u FROM User u WHERE u.userName = :userName AND u.password = :password") User findByUserNameAndPassword2(@Param("userName") String userName, @Param("password") String password); // 使用@Modifying进行修改操作 @Modifying @Transactional @Query("UPDATE User u SET u.password = :password WHERE u.id = :id") void updatePasswordById(@Param("id") Long id, @Param("password") String password); }
复杂查询
分页和排序查询
其中Sort.by("age")会默认按照年龄升序排序,如果需要升序排序,可以使用Sort.by("age",<Sort.Direction>)进行指定,也可以使用Sort.by("age").descending()进行升序排序
public interface UserRepository extends Repository<User, Long> { // 根据年龄降序分页查询用户列表 Page<User> findBy(Pageable pageable); } // 使用 Pageable pageable = PageRequest.of(0, 10, Sort.by("age").descending()); Page<User> page = userRepository.findBy(pageable); List<User> userList = page.getContent();
动态查询
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> { List<User> findAll(Specification<User> spec); }
定义Specification
// 使用Lambda表达式简单定义年龄大于18的用户 Specification<User> spec = (user, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(user.get("age"), 18); userRepostory.findAll(spec); // 使用匿名方法进行组合动态查询 public List<User> findUsersByCondition(String username, String email, Integer age) { return userRepository.findAll((root, query, criteriaBuilder) -> { List<Predicate> list = new ArrayList<>(); if (StringUtils.isNotBlank(username)) { list.add(criteriaBuilder.equal(root.get("username"), username)); } if (StringUtils.isNotBlank(email)) { list.add(criteriaBuilder.like(root.get("email"), "%" + email + "%")); } if (age != null) { list.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), age)); } Predicate[] predicates = list.toArray(new Predicate[list.size()]); return criteriaBuilder.and(predicates); }); }
事务管理
声明式事务管理,简单,但是不灵活
@Service @Transactional(rollbackFor = Exception.class) public class UserService { @Autowired private UserRepository userRepository; // 添加用户 public User addUser(User user) { return userRepository.save(user); } // 更新用户 public void updateUser(User user) { userRepository.save(user); } // 删除用户 public void deleteUser(Long id) { userRepository.deleteById(id); } }
编程式事务管理,复杂,需要手动提交、回滚等,但是可以实现细粒度的回滚等操作
@Service public class UserService { @Autowired private UserRepository userRepository; @Transactional(rollbackFor = Exception.class) public void addUser(User user) { userRepository.save(user); } @Transactional(rollbackFor = Exception.class) public void updateUser(User user) { userRepository.save(user); } @Transactional(rollbackFor = Exception.class) public void deleteUser(Long id) { userRepository.deleteById(id); } }
数据库操作
初始化
使用JPA的代码会自动在数据库创建相应表格进行初始化
升级
导入依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>7.6.3</version>
</dependency>
在src/main/resources/db/migration编写数据库表更新语句
ALTER TABLE `user` ADD `address` VARCHAR(255) NULL AFTER `age`;
数据库更新相关配置application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 # Flyway 数据库升级相关配置 spring.flyway.locations=classpath:/db/migration spring.flyway.baselineOnMigrate=true
多数据库作为数据源
在application.properties中配置多数据源
# 数据源一 spring.datasource.one.url=jdbc:mysql://localhost:3306/test1 spring.datasource.one.username=root spring.datasource.one.password=123456 # 数据源二 spring.datasource.two.url=jdbc:mysql://localhost:3306/test2 spring.datasource.two.username=root spring.datasource.two.password=123456
创建两个数据源的配置类
@Configuration @ConfigurationProperties(prefix = "spring.datasource.one") public class DataSourceOneConfig { private String url; private String username; private String password; // 省略 getter 和 setter 方法 @Bean public DataSource dataSourceOne() { return DataSourceBuilder.create() .url(url) .username(username) .password(password) .build(); } } @Configuration @ConfigurationProperties(prefix = "spring.datasource.two") public class DataSourceTwoConfig { private String url; private String username; private String password; // 省略 getter 和 setter 方法 @Bean public DataSource dataSourceTwo() { return DataSourceBuilder.create() .url(url) .username(username) .password(password) .build(); } }
使用指定数据源
@Service public class UserService { @Autowired @Qualifier("dataSourceOne") private DataSource dataSourceOne; @Autowired @Qualifier("dataSourceTwo") private DataSource dataSourceTwo; public void addUser(User user) { try (Connection connection = dataSourceOne.getConnection(); PreparedStatement statement = connection.prepareStatement( "INSERT INTO user (name, age) VALUES (?, ?)" ) ) { statement.setString(1, user.getName()); statement.setInt(2, user.getAge()); statement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } }
MyBatis使用
MyBatis的使用逻辑:导入依赖,编写xml文件(用于编写SQL相关逻辑,需要在application配置文件中Mybatis的配置中声明xml文件的位置,同时在xml文件中通过名字空间与mapper类绑定,通过xml中的具体SQL内容的id与mapper类中方法名相同的方式绑定mapper的方法与执行逻辑),编写mapper类(通过注解@mapper的方式声明本类是MyBatis的持久层),调用mapper(注入mapper之后,调用mapper的方法时,mybatis会通过类名和方法名去定位application文件中声明的文件夹内定义的xml文件中的相关执行逻辑)

配置前置条件
- 创建数据库和数据表
- 创建属性与表格字段一一对应的实体类
- 创建实体类的mapper接口(可先为无方法的接口)
导入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
在application.yml中配置数据源
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: http://127.0.0.1:3306/two?characterEncoding=utf8&useSSL=true&serverTimezone=GMT&2B8&createDatabaseIfNotExist=true name: two password: Two2024
# 指定xml文件所在位置
mybatis:
mapper-locations: classpath:mapper/*.xml

编写映射xml文件
映射文件中的内容如下
- cache – 给定命名空间的缓存配置;
- cache-ref – 其他命名空间缓存配置的引用;
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象;
- sql – 可被其他语句引用的可重用语句块;
- insert – 映射插入语句;
- update – 映射更新语句;
- delete – 映射删除语句;
- select – 映射查询语句;
固定模板
<?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.demo.mapper.UserInfoXMLMapper">
</mapper>

在模板中编写定制语句
定制语句中的属性含义
- mapper:代表一个mapper中的相关执行逻辑,通过namespace与mapper进行绑定
- resultType:返回结果的类型
- parameterType:参数类型
结果映射
将表格的字段和类的属性进行对应
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo"> <id column="id" property="id"></id> <result column="delete_flag" property="deleteFlag"></result> <result column="create_time" property="createTime"></result> <result column="update_time" property="updateTime"></result> </resultMap> <select id="selectAllUser" resultMap="BaseMap"> select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo </select>
查询
定义返回值类型为对象:映射为对象,直接将字段值注入到相应的属性中
定义返回值类型为map:
<?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.mybatisdemo.mapper.UserInfoXMLMapper">
<select id="selectAll" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo
</select>
</mapper>
<?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.mybatisdemo.mapper.UserInfoXMLMapper"> <select id="selectUser" parameterType="int" resultType="hashmap"> select * from t_user where id = #{id} </select> </mapper>
新增
@Mapper public interface UserInfoXMLMapper { Integer insertUser(UserInfo userInfo); }
<insert id="insertUser"> insert into userinfo (username, password, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone}) </insert>
异常
报错:Field userMapper inUserServiceImpl required a bean of type xxxMapper that could not be found.
情况一:查看是否在application中指定了mybatis的mapper所在的位置,代码如下:
mybatis: mapper-locations: classpath:com/yy/mapper/xml/*.xml
情况二:对mapper接口使用@Mapper注解,或者在启动类使用@MapperScan("com.yy.mapper.xml")来指定mapper的xml文件所在位置
@MapperScan("com.yy.mapper")
public class Starter extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Starter.class,args);
}
}
情况三:检查是不是在启动类的@SpringBootApplication后面加参数了
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class,scanBasePackages = "com.*")
将括号包括里面的内容全部删除即可
参考
Jpa查询MyBatis:XML操作
报错:Field userMapper inUserServiceImpl required a bean of type xxxMapper that could not be found.
这下够清楚了吧!详解Mybatis的Mapper映射文件 Mybatis教程 | 第三篇:深入Mapper映射文件
SpringData JPA:一文带你搞懂
浙公网安备 33010602011771号