持久层是Java EE访问数据库的核心操作,SpringBoot中对常见的持久层框架都提供了自动化配置,如Spring内置的jdbc Template、JPA等,而Mybatis的自动化配置则是由Mybatis官方提供的。本篇就来学习SpringBoot如何整合这几种持久层技术,既然是整合那么对于这些持久层的用法就不会有较复杂的介绍。个人建议研究Mybatis和JPA就足矣,jdbc Template可以忽略。

整合jdbc Template

Jdbc Template是Spring内置的一套JDBC模板框架,使用AOP技术来解决直接使用JDBC时需要大量重复代码的问题。Jdbc Template虽然没有Mybatis那么灵活,但是比直接使用JDBC要方便很多。SpringBoot中对Jdbc Template的使用提供了自动化配置类JdbcTemplateAutoConfiguration,查看一下它的部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({JdbcProperties.class})
public class JdbcTemplateAutoConfiguration {
......
@Configuration
static class JdbcTemplateConfiguration {
private final DataSource dataSource;
private final JdbcProperties properties;

JdbcTemplateConfiguration(DataSource dataSource, JdbcProperties properties) {
this.dataSource = dataSource;
this.properties = properties;
}

@Bean
@Primary
@ConditionalOnMissingBean({JdbcOperations.class})
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
Template template = this.properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int)template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}
}
}

从这段源码可知,在JdbcTemplateAutoConfiguration类内定义了一个静态内部类。当classpath下存在DataSourceJdbcTemplateDataSource中只有一个实例时,自动配置才会生效,若开发者没有提供JdbcOperations则SpringBoot会自动向容器中注入一个JdbcTemplate,只需要提供DataSource依赖和JdbcTemplate的依赖即可。具体的操作步骤如下:

第一步,创建数据库和表。此处使用Mysql数据库,在查询控制台执行以下语句:

1
2
3
4
5
6
7
8
9
10
11
12
drop database if exists sqlbook;
create database sqlbook;
use sqlbook;
drop table if exists book;
create table book(
id int(11) not null auto_increment comment 'id',
name varchar(128) default null comment '名称',
author varchar(64) default null comment '作者',
primary key(id)
)ENGINE=INNODB default charset=utf8;

insert into book(id,name,author)values(1,"西游记","吴承恩"),(2,"红楼梦","曹雪芹");

第二步,创建SpringBoot项目并添加依赖。使用spring Initializr构建工具构建一个SpringBoot的Web应用,名称为sqlspringboot,然后在pom.xml文件中添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加jdbc依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--添加数据库驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--添加数据库连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.25</version>
</dependency>

请注意spring-boot-starter-jdbc这个启动类中提供了spring-jdbc,另外还需要添加数据库启动和数据库连接池依赖。

第三步,数据配置。application.properties配置文件中配置数据库的基本信息:

1
2
3
4
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/sqlbook?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234

第四步,创建实体类。新建pojo包,接着在里面新建Book实体类,里面的代码为:

1
2
3
4
5
6
public class Book {
private Integer id;
private String name;
private String author;
//getter、setter、toString和无参构造方法
}

**第五步,新建数据库访问层(repository层,ssm中一般称为dao层)**。新建repository包,接着在里面新建BookDao类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//增加
public int addBook(Book book){
String sql = "insert into book(name,author)values(?,?)";
//result为执行结果,也就是受影响的行数
int result = jdbcTemplate.update(sql,book.getName(),book.getAuthor());
return result;
}
//删除
public int deleteBookById(Integer id){
String sql = "delete from book where id= ?";
//result为执行结果,也就是受影响的行数
int result = jdbcTemplate.update(sql,id);
return result;
}
//修改
public int updateBook(Book book){
String sql = "update book set name=?,author=? where id=?";
//result为执行结果,也就是受影响的行数
int result = jdbcTemplate.update(sql,book.getName(),book.getAuthor(),book.getId());
return result;
}
//根据id查询
public Book findBookById(Integer id){
String sql = "select * from book where id=?";
List<Book> lists= jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Book.class),id);
return lists.get(0);
}
//全查询
public List<Book> findBook(){
String sql = "select * from book";
List<Book> lists = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Book.class));
return lists;
}
}

解释一下上述代码的含义:前面提到过,当你没有提供JdbcOperations时,SpringBoot会自动注入一个JdbcTemplate对象到Spring容器中,因此可以自动导入使用。在Jdbc Template中增删改三种类型操作主要使用update和batchUpdate方法来完成。而查这个操作就必须使用query和queryForObject方法来完成。当然还有execute方法可以执行任意的sql、call方法用来调用存储过程等。请注意在查询时,第二个参数是一个RowMapper对象,用于将查询出来的列和实体类中的属性一一对应起来。如果查询出来的列名和实体类中的属性名一致,那么可以直接使用BeanPropertyRowMapper;如果列名与属性名不一致,那就需要开发者自己实现RowMapper接口,进而将列和实体类属性一一对应起来。

第六步,创建业务逻辑层(Service层)。新建service包,接着在里面新建BookService类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class BookService {
@Autowired
private BookDao bookDao;

public int addBook(Book book){
return bookDao.addBook(book);
}

public int deleteBookById(Integer id){
return bookDao.deleteBookById(id);
}

public int updateBook(Book book){
return bookDao.updateBook(book);
}

public Book findBookById(Integer id){
return bookDao.findBookById(id);
}

public List<Book> findBook(){
return bookDao.findBook();
}
}

上述代码的含义非常简单,调用repository层的方法来实现相应的功能。由于这里的业务逻辑较为简单,因此可能觉得Service层没有存在的必要,但是对于复杂的业务逻辑,使用service层还是有必要的。

第七步,创建访问控制层(Controller层)。新建controller包,接着在里面新建BookController类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestController
public class BookController {
@Autowired
private BookService bookService;
@GetMapping("/bookOps")
public void bookOps(){
//演示增加方法
Book book1 = new Book();
book1.setName("三国演义");
book1.setAuthor("罗贯中");
int addResult = bookService.addBook(book1);
System.out.println("addBook>>>>>> "+addResult);

//演示修改方法
Book book2 = new Book();
book2.setId(1);
book2.setName("水浒传");
book2.setAuthor("施耐庵");
int updateResult = bookService.updateBook(book2);
System.out.println("updateBook>>>>>> "+updateResult);

//演示根据id查询方法
Book book3 = bookService.findBookById(3);
System.out.println("findBookById>>>>>> "+book3);

//演示删除方法
int deleteResult = bookService.deleteBookById(3);
System.out.println("deleteBookById>>>>>> "+deleteResult);

//演示全查询方法
List<Book> allBooks = bookService.findBook();
System.out.println("findBook>>>>>> "+allBooks);
}
}

第八步,运行项目。运行项目,在浏览器地址栏中输入http://localhost:8080/bookOps,然后查看控制台的输出信息为:

整合Mybatis

Mybatis是一款优秀的持久层框架,支持定制化SQL、动态SQL及高级映射。Mybatis几乎避免了所有的JDBC代码手动设置参数以及获取结果集。笔者之前使用过Spring、SpringMVC、Mybatis(SSM)框架,在使用Mybatis时需要配置大量的XML文件。而在SpringBoot中,Mybatis官方提供了一套自动化配置方案,实现了Mybatis开箱即用的目标。具体的操作步骤如下:

第一步,创建SpringBoot项目。使用spring Initializr构建工具构建一个SpringBoot的Web应用。笔者就不创建了,继续使用前面的sqlspringboot项目。不过pom.xml文件中的依赖需要修改为如下信息(注释掉前面的信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <!--Mybatis演示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--添加数据库驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--添加数据库连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.25</version>
</dependency>

请注意spring-boot-starter-jdbc这个启动类中提供了spring-jdbc,另外还需要添加数据库启动和数据库连接池依赖。

第二步,创建数据库、表、实体类。数据库、表、实体类及application.properties中的数据库配置信息都与前面Jdbc Template中的完全一样,这里就不再赘述。

第三步,创建数据库访问层。新建mapper包,接着在里面新建一个BookMapper接口(注意名称必须是实体类大写+Mapper这种格式),里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Mapper
public interface BookMapper {
//增加
int addBook(Book book);
//删除
int deleteBookById(Integer id);
//修改
int updateBook(Book book);
//根据id查询
Book findBookById(Integer id);
//全查询
List<Book> findBook();
}

解释一下上述代码的含义:新建一个mapper包,并在其中创建一个BookMapper接口。注意有两种方式指明该类是一个Mapper:第一种如前面的代码所示,在BookMapper接口上添加@Mapper注解,以表明该接口是一个Mybatis中的Mapper,这种方式需要在每一个Mapper上都添加注解。还有一种比较简单的方式就是在配置类上添加@MapperScan("com.envy.sqlspringboot.mapper")注解,表示扫描com.envy.sqlspringboot.mapper包下的所有接口作为Mapper,这样就不需要在每个接口上都配置一个@Mapper注解。举个例子,可以新建config包,接着在里面新建一个MyConfigMapper配置类,里面的代码为:

1
2
3
4
@Configuration
@MapperScan("com.envy.sqlspringboot.mapper")
public class MyConfigMapper {
}

第四步,创建BookMapper.xml文件。请注意必须在与BookMapper接口相同的包内新建BookMapper.xml文件,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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.envy.sqlspringboot.mapper.BookMapper">
<insert id="addBook" parameterType="com.envy.sqlspringboot.pojo.Book">
insert into book(name,author)values(#{name},#{author})
</insert>

<delete id="deleteBookById" parameterType="int">
delete from book where id =#{id}
</delete>

<update id="updateBook" parameterType="com.envy.sqlspringboot.pojo.Book">
update book set name=#{name},author=#{author} where id=#{id}
</update>

<select id="findBookById" parameterType="int" resultType="com.envy.sqlspringboot.pojo.Book">
select * from book where id =#{id}
</select>

<select id="findBook" resultType="com.envy.sqlspringboot.pojo.Book">
select * from book
</select>
</mapper>

解释一下上述代码的含义:在xml文件中需要对接口文件中的每一个方法进行实现,其中xml文件中的id就是接口中的方法名称。使用#{}来代替接口中的参数,实体类中的属性可以直接通过#{实体类属性}来获取。

第五步,创建Service层。在service包内新建MybatisBookService类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class MybatisBookService {
@Autowired
private BookMapper bookMapper;

public int addBook(Book book){
return bookMapper.addBook(book);
}

public int deleteBookById(Integer id){
return bookMapper.deleteBookById(id);
}

public int updateBook(Book book){
return bookMapper.updateBook(book);
}

public Book findBookById(Integer id){
return bookMapper.findBookById(id);
}

public List<Book> findBook(){
return bookMapper.findBook();
}
}

第六步,创建Controller层。在controller包内新建MybatisBookController类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RestController
@RequestMapping("/mybatis")
public class MybatisBookController {
@Autowired
private MybatisBookService mybatisBookService;
@GetMapping("/bookOps")
public void bookOps(){
//演示增加方法
Book book1 = new Book();
book1.setName("三国演义");
book1.setAuthor("罗贯中");
int addResult = mybatisBookService.addBook(book1);
System.out.println("addBook>>>>>> "+addResult);

//演示修改方法
Book book2 = new Book();
book2.setId(1);
book2.setName("水浒传");
book2.setAuthor("施耐庵");
int updateResult = mybatisBookService.updateBook(book2);
System.out.println("updateBook>>>>>> "+updateResult);

//演示根据id查询方法
Book book3 = mybatisBookService.findBookById(3);
System.out.println("findBookById>>>>>> "+book3);

//演示删除方法
int deleteResult = mybatisBookService.deleteBookById(3);
System.out.println("deleteBookById>>>>>> "+deleteResult);

//演示全查询方法
List<Book> allBooks = mybatisBookService.findBook();
System.out.println("findBook>>>>>> "+allBooks);
}
}

第七步,配置pom.xml文件,这一步很重要。在Maven工程中,XML配置文件建议写在resources目录下,但是很明显上述的BookMapper.xml文件写在了mapper包内,且不在resources目录下,此时运行项目肯定会抛出mapper文件找不到的异常,因为Maven运行时会忽略包内的xml文件,因此需要在pom.xml文件中重新指明资源文件的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <build>
<!--使用mybatis时需要手动指明xml的位置-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>

第八步,运行项目。运行项目,在浏览器地址栏中输入http://localhost:8080/mybatis/bookOps,然后查看控制台的输出信息为:

请注意当mappers的映射为resources相对于类路径的资源引用方式正确,就像第七步中的配置一般,则mapper接口名称与mapper.xml名称可以不一致,只要xml中配置的namespce与接口的全名对应即可。

通过上面的例子可以看出,Mybatis基本上实现了开箱即用的特性。自动化配置将开发这从繁杂的配置文件中解脱出来,使其更加专注于业务逻辑的开发。讲到这里推荐一个工具Mybatis-Plus,它是Mybatis框架的一个增强插件,根据官方描述Mybatis-Plus只做增强不做改变,因此引入它不会对现有工程产生影响,后续会专门有文章介绍它的使用。

整合Spring Data JPA

JPA(Java Persistence API)和Spring Data是两个范畴的概念。

作为一名java开发人员,基本上都听说过Hibernate框架。Hibernate是一个ORM框架,而JPA则是一种ORM规范,JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,即JPA指定了ORM规范,而Hibernate则是这些规范的实现(事实上是先有Hibernate后又JPA,JPA规范的起草者就是Hibernate的作者),因此从功能上来说,JPA相当于Hibernate的一个子集。

Spring Data是Spring的一个子项目,致力于简化数据库访问,通过规范的方法来分析开发者的意图,进而减少数据库访问层的代码量。Spring Data不仅支持关系型数据库,也支持非关系型数据库。Spring Data JPA可以有效的简化关系型数据库访问代码。SpringBoot整合Spring Data JPA的步骤如下:

第一步,创建数据库,不创建数据表。请注意使用Spring Data JPA只需要创建数据库,无需创建数据表,使用下述代码来创建一个名为jpa的数据库:

1
2
drop database if exists jpa;
create database jpa default character set utf8;

第二步,创建SpringBoot项目。使用spring Initializr构建工具构建一个SpringBoot的Web应用,名称为jpaspringboot,然后在pom.xml中添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--Spring Data JPA演示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加数据库驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--添加数据库连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.25</version>
</dependency>
<!--添加JPA依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--添加lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

请注意JPA的顺序必须晚于数据库驱动的导入顺序。lombok可以自动给实体类的属性提供getter/setter方法,及toString,构造方法等。

第三步,数据库配置。application.properties配置文件中添加数据库基本信息及JPA相关配置:

1
2
3
4
5
6
7
8
9
10
11
# 数据库相关胚子
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/jpa?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234

# JPA相关配置
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

上面的配置信息主要分为两大类,第1-4行是数据库基本信息配置,第5-8行是JPA相关配置。其中spring.jpa.show-sql=true表示在控制台打印JPA执行过程生成的SQL;spring.jpa.database=mysql表示JPA对应的数据库为MySQL;spring.jpa.hibernate.ddl-auto=update表示在项目启动时根据实体类更新数据库中的表(可选值有create、create-drop、validate、no);spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect表示使用的数据库方言是MySQL57Dialect

第四步,创建实体类。新建pojo包,并在其中创建一个Book实体类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity(name = "t_book")
@Data
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Column(name = "book_name",nullable = false)
private String name;

private String author;

private Float price;

@Transient
private String description;
}

解释一下上述代码的含义:

  • @Entity注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认使用该类名作为表名。
  • @Data注解表示lombok会自动给这个实体类提供getter、setter 、toString等方法。所有的实体类都要有主键,@Id注解表示该属性是一个注解,@GeneratedValue注解表示主键自动生成,strategy表示主键的生成策略。
  • 默认情况下,生成的表中字段的名称就是实体类中属性的名称,但是开发者可以通过@Column注解来定制生成的字段的属性,name表示该属性对应的数据表中字段的名称,nullable表示该字段非空。
  • @Transient注解非常实用,表示在生成数据库中的表时,该属性会被忽略,即不会生成对应的字段。

第五步,创建BookDao接口。新建repository包,接着在里面创建BookDao接口,并继承JpaRepository接口,相应的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**里面有两个参数,第一个是数据库的实体类名称,第二个是id的类型**/
public interface BookDao extends JpaRepository<Book,Integer> {
List<Book> getBooksByAuthorStartingWith(String author);
List<Book> getBooksByPriceGreaterThan(Float price);

@Query(value = "select * from t_book where id =(select max(id) from t_book)",nativeQuery = true)
Book getMaxIdBook();

@Query(value = "select b from t_book b where b.id>:id and b.author=:author")
List<Book> getBookByIdAndAuthor(@Param("author")String author,@Param("id")Integer id);

@Query(value = "select b from t_book b where b.id<?2 and b.name like %?1%")
List<Book> getBooksByIdAndName(String name,Integer id);
}

解释一下上述代码的含义:

  • 自定义BookDao,继承自JpaRepository,其中是一个泛型,里面有两个参数,第一个是数据库的实体类名称,第二个是id的类型。JpaRepository中提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等。

  • getBooksByAuthorStartingWith方法表示查询以某个字符开始的所有书。

  • getBooksByPriceGreaterThan方法表示查询单价大于某个值的所有书。

  • 在Spring Data JPA中,只要方法的定义符合既定的规范,Spring Data就能分析出开发者的意图,从而避免开发者自己定义SQL。所谓的既定规范其实就是一定的方法命名规则。支持的命名规则如下表所示:

    关键词 方法名称举例 对应的SQL
    And findByNameAndAge where name=? and age=?
    Or findByNameOrAge where name=? or age=?
    Is findByAgeIs where age = ?
    Equals findByIdEquals where id = ?
    Between finfByAgeBetween where age between ? and ?
    LessThan finfByAgeLessThan where age < ?
    LessThanEquals finfByAgeLessThanEquals where age <= ?
    GreaterThan finfByAgeGreaterThan where age > ?
    GreaterThanEquals finfByAgeGreaterThanEquals where age >= ?
    After findByAgeAfter where age > ?
    Before findByAgeBefore where age < ?
    IsNull findByNameIsNull where name is null
    IsNotNull/NotNull findByNameIsNotNull where name is not null
    Not findByGenderNot where gender <> ?
    In findByAgeIn where age in (?)
    NotIn findByAgeNotIn where age not in (?)
    NotLike findByNameNotLike where name not like ?
    Like findByNameLike where name like ?
    StartingWith findByNameStartingWith where name like ‘?%’
    EndingWith findByNameEndingWith where name like ‘%?’
    Containing/Contains findByNameContaining where name like ‘%?%’
    OrderBy findByAgeGreaterThanOrderByIdDesc where age >? order by id desc
    True findByEnabledTrue where enabled = true
    False findByEnabledFalse where enabled = false
    IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)

解释一下上述代码的含义:

  • 上述就是既定的方法命令规则,当然如果不满足开发者需求,Spring Data JPA也是支持自定义JPQL(Java Persistence Query Language)或者原生的SQL。在BookDao接口中,笔者定义了getMaxIdBook方法,并在上面使用@Query注解,并书写了一行sql语句用于查询id最大的书,请注意此处的sql由于是原生SQL,因此后面需要使用nativeQuery = true进行指明。
  • getBookByIdAndAuthor方法用于根据id和author来查询书籍信息,这里使用了默认的JPQL语句。JPQL是一种面向对象表达式语言,可以将SQL语法和简单查询语义绑定在一起,使用这种语言编写的查询是可以移植的,可以被编译成所有主流数据库服务器上的SQL。JPQL与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性(如果开发者之前用过Hibernate可能觉得这里的JPQL类似于HQL)。在该条JPQL语句中select b from t_book b where b.id>:id and b.author=:author,使用:id:author这种方式来进行参数绑定。注意这里使用的列名是实体类的属性名,而不是数据库中数据表列的名称。
  • getBooksByIdAndName方法也是自定义的JPQL查询,不同的是传参方式使用的是?1、?2这种方式。请注意这里方法中参数的顺序要与参数声明的顺序保持一致。因此笔者不建议使用这种,推荐使用参数注解的方式,这样可以避免因参数顺序带来的错误。
  • 如果BookDao中的方法涉及到修改操作,就需要添加@Modifying注解,并在对应的方法上添加事务注解@Transcational

第六步,创建BookService类。新建service包,并在里面创建BookService类,其中的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class BookService {
@Autowired
private BookDao bookDao;

public void addBook(Book book){
bookDao.save(book);
}
public Page<Book> getBookByPage(Pageable pageable){
return bookDao.findAll(pageable);
}

public List<Book> getBooksByAuthorStartingWith(String author){
return bookDao.getBooksByAuthorStartingWith(author);
}

public List<Book> getBooksByPriceGreaterThan(Float price){
return bookDao.getBooksByPriceGreaterThan(price);
}

public Book getMaxIdBook(){
return bookDao.getMaxIdBook();
}

public List<Book> getBookByIdAndAuthor(String author, Integer id){
return bookDao.getBookByIdAndAuthor(author, id);
}

public List<Book> getBooksByIdAndName(String name,Integer id){
return bookDao.getBooksByIdAndName(name, id);
}
}

解释一下上述代码的含义:

  • 请注意在Spring Data JPA中,保存和修改数据都是使用save方法,save方法是JpaRepository接口提供的。
  • getBookByPage方法是一个分页查询,调用了findAll方法,返回值为Page<Book>,该对象中包含分页常用的数据,如总记录数,总页数,每页记录数、当前页记录数等。

第七步,创建BookController类。新建controller包,并在里面创建BookController类,并对代码进行测试。其中的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RestController
@Slf4j
public class BookController {

@Autowired
private BookService bookService;

@GetMapping("/findAll")
public void findAll(){
PageRequest pageable = PageRequest.of(1,3);
Page<Book> bookPage = bookService.getBookByPage(pageable);
log.info("【总页数】totalPages ={}",bookPage.getTotalPages());
log.info("【总记录数】totalElements ={}",bookPage.getTotalElements());
log.info("【查询结果】content={}",bookPage.getContent());
log.info("【当前页数】number={}",bookPage.getNumber()+1); //从0开始
log.info("【当前页记录数】numberOfElements={}",bookPage.getNumberOfElements());
log.info("【每页记录数】size={}",bookPage.getSize());
}

@GetMapping("/search")
public void search(){
List<Book> books1 = bookService.getBookByIdAndAuthor("鲁迅",5);
List<Book> books2 = bookService.getBooksByAuthorStartingWith("吴");
List<Book> books3 = bookService.getBooksByIdAndName("水",6);
List<Book> books4 = bookService.getBooksByPriceGreaterThan(160F);
Book book = bookService.getMaxIdBook();
log.info("【getBookByIdAndAuthor】books1={}",books1);
log.info("【getBooksByAuthorStartingWith】books2={}",books2);
log.info("【getBooksByIdAndName】books3={}",books3);
log.info("【getBooksByPriceGreaterThan】books4={}",books4);
log.info("【getMaxIdBook】book={}",book);
}

@GetMapping("/save")
public void save(){
Book book = new Book();
book.setName("彷徨");
book.setAuthor("鲁迅");
book.setPrice(118F);
book.setDescription("一本让人精神抖擞的良书。");
bookService.addBook(book);
}
}

第八步,运行项目,并新增测试数据。运行项目,并在数据库中新增以下测试数据:

1
insert into t_book(book_name,author,price)values("红楼梦","曹雪芹",188),("西游记","吴承恩",168),("三国演义","罗贯中",158),("水浒传","施耐庵",148),("狂人日记","鲁迅",138),("且介亭文集","鲁迅",128);

第九步,测试相关接口。在浏览器地址栏中输入http://localhost:8080/findAll链接时,查看控制台的信息输出为:

接着在浏览器地址栏中输入http://localhost:8080/save链接时,查看控制台的信息输出为:

同时发现数据库已经新增了一条信息:

最后在浏览器地址栏中输入http://localhost:8080/search链接时,查看控制台的信息输出为:

这样关于SpringBoot整合持久层技术就学习到这,后续是如何配置多数据源。