简介

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语句,但是要遵守规则的方法名

KeywordSampleJPQL 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
之后Spring Boot启动后Flyway会自动更新数据库

多数据库作为数据源

在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映射文件

Mybatis教程 | 第三篇:深入Mapper映射文件
SpringData JPA:一文带你搞懂