您的位置:首页 > 编程语言 > Java开发

第三章 Spring Data JPA

2017-08-06 00:00 429 查看
本章是《 Spring Boot 快速入门 》系列教程的第三章,若要查看本系列的全部章节,请点击 这里

目录

简介

源码下载

软件版本

JPA简介

在项目中配置JPA

编写实体类

编写 Repository 接口

使用原生SQL查询

总结说明

简介

在上一章《 Spring Boot MVC 》中,我们了解了使用 Spring Boot MVC 来开发 Http Restful API的相关技术,但只处理HTTP请求是不够的,现在的应用程序大多使用了关系型数据库,因此本章我们会带着大家继续 Spring Boot 体验之旅,这次我们将采用 JPA 技术来访问数据库,给 Hello Spring Boot 程序添加带数据库访问演示代码。

源码下载

本章的示例代码放在“码云”上,大家可以免费下载或浏览:

https://git.oschina.net/terran4j/springboot/tree/master/springboot-jpa

软件版本

相关软件使用的版本:

Java: 1.8

Maven: 3.3.9

MYSQL: 5.5

程序在以上版本均调试过,可以正常运行,其它版本仅作参考。

JPA简介

JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体“对象持久化”到数据库中。
JPA技术可以极大的降低了对数据库编程的复杂性,一些简单的增删改查的操作,代码只需要操作对象就可以了,JPA自动的帮你映射成数据库的SQL操作。

不过 JPA 只是标准标准,而 Spring Boot 提供了它的技术实现: Spring Data JPA。不过 Spring Data JPA 也不是重复造轮子,它是基于一个非常著名的ORM框架——Hibernate——之上封装实现的。

Spring Data JPA 极大简化了数据库访问层代码,只要3步,就能搞定一切:

在pom.xml中配置
spring-boot-starter-data-jpa
,及在 application配置文件中配置数据库连接。

编写 Entity 类,依照 JPA 规范,定义实体。

编写 Repository 接口,依靠 Spring Data 规范,定义数据访问接口(注意,只要接口,不需要任何实现)

另外,如果有复杂的SQL查询,Spring Data JPA 也提供了编写原生 SQL 实现的方式。
 

在项目中配置JPA

首先,我们要在 pom.xml 文件中添加
spring-boot-starter-data-jpa
的依赖,代码如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<groupId>terran4j</groupId>
<artifactId>springboot-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>springboot-jpa</name>
<url>https://git.oschina.net/terran4j/springboot/tree/master/springboot-jpa</url>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 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>
</dependencies>

</project>

注意新增的两个依赖,一个是
spring-boot-starter-data-jpa
,它集成了JPA相关的 jar 包;另一个是
mysql-connector-java
, 因为本示例中我们要连MYSQL的数据库,所以 mysql jdbc 驱动(java) 是必不可少的。

然后,我们要在
application.properties
配置文件中配置数据库连接及JPA配置,如下所示:

spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username = root
spring.datasource.password =

spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql = true

spring.datasource
开头的是数据库连接的配置,请注意一定要保持对应的数据库是存在的,并且用户名密码都没错,不然待会程序运行时无法启动。
spring.jpa
开发的是 JPA 的配置,
spring.jpa.hibernate.ddl-auto
表示每次程序启动时对数据库表的处理策略,有以下可选值:

create:
每次程序启动时,都会删除上一次的生成的表,然后根据你的实体类再重新来生成新表,哪怕两次没有任何改变也要这样执行。
这种策略适合于执行自动化测试的环境下使用,其它环境请慎用。

create-drop :
每次程序启动时,根据实体类生成表,但是程序正常退出时,表就被删除了。

update:
最常用的属性,第一次程序启动时,根据实体类会自动建立起表的结构(前提是先建立好数据库),以后程序启动时会根据实体类自动更新表结构,即使表结构改变了,但表中的记录仍然存在,不会删除以前的记录。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 第一次访问JPA时后才会建立。

validate :
每次程序启动时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

因此,建议大多数场景下用 update 就可以了,线上环境(或需要慎重的环境)中用 validate 会更保险一些,没有特殊情况下不建议用 create 及 create-drop 模式。

配置完成后,我们运行下 main 程序(代码如下):

@SpringBootApplication
public class HelloJPAApp {

public static void main(String[] args) {
SpringApplication.run(HelloJPAApp.class, args);
}

}

结果控制台输入里多了一些东西:

......
2017-08-04 15:51:27.017  INFO 20248 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.0.12.Final}
2017-08-04 15:51:27.018  INFO 20248 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2017-08-04 15:51:27.020  INFO 20248 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2017-08-04 15:51:27.086  INFO 20248 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2017-08-04 15:51:27.666  INFO 20248 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
2017-08-04 15:51:28.230  INFO 20248 --- [           main] org.hibernate.tool.hbm2ddl.SchemaUpdate  : HHH000228: Running hbm2ddl schema update
2017-08-04 15:51:28.424  INFO 20248 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
......

如果控制台输出中没有报错,并且有这之类的输出,表示配置成功了。

编写实体类

要操作数据库数据,首先得建表。然而 JPA 使用起来非常简单,简单得你只需要在Java的实体类上加上一些注解,就可以自动映射成数据库表。

下面是一个实体类的代码:

package com.terran4j.springboot.jpa;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity(name = "t_user") // 定义数据库表名称。
@Table(indexes = { // 定义数据库索引。

// 唯一索引。
@Index(name = "ux_user_login_name", columnList = "loginName", unique = true), //

// 非唯一索引。
@Index(name = "idx_user_age", columnList = "age"), //
})
public class User {

/**
* id, 自增主键。
*/
@Id
@GeneratedValue
@Column(length = 20)
private Long id;

/**
* 用户的登录名。
*/
@Column(length = 100, nullable = false)
private String loginName;

/**
* 用户的年龄。
*/
@Column(length = 3)
private Integer age;

/**
* 用户的状态。
*/
@Column(length = 16, nullable = false)
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.enable;

/**
* 用户的注册时间。
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(nullable = false)
private Date registTime;

public final Long getId() {
return id;
}

public final void setId(Long id) {
this.id = id;
}

public final String getLoginName() {
return loginName;
}

public final void setLoginName(String loginName) {
this.loginName = loginName;
}

public final Integer getAge() {
return age;
}

public final void setAge(Integer age) {
this.age = age;
}

public final UserStatus getStatus() {
return status;
}

public final void setStatus(UserStatus status) {
this.status = status;
}

public final Date getRegistTime() {
return registTime;
}

public final void setRegistTime(Date registTime) {
this.registTime = registTime;
}

}

首先,我们看 User 类上两个注解 @Entity@Table :
@Entity(name = "t_user") 注解 加在 User 上,表示它是一个实体类, 表名是 t_user 。

@Table(indexes = { // 定义数据库索引。

// 唯一索引。
@Index(name = "ux_user_login_name", columnList = "loginName", unique = true), //

// 非唯一索引。
@Index(name = "idx_user_age", columnList = "age"), //
})

@Table 里面定义了这个表的索引,一个 @Index 注解定义了一个索引, name 属性表示数据库表中索引的名称, columnList 表示对应的 java 属性名称, unique = true 表示此索引是唯一索引。
比如上面的
@Index(name = "ux_user_login_name", columnList = "loginName", unique = true)
表示对 loginName 属性所对应的字段(映射到数据库表中应该是 login_name 字段)建立唯一索引,索引名为ux_user_login_name。
columnList 中可以放多个java属性,中间用逗号隔开,表示联合索引,如:
@Index(name = "idx_user_age_name", columnList = "age,loginName")
表示建立 age 与 login_name 字段的联合索引。

注意: java 属性名都是驼峰命名法(如 loginName),而数据库表字段都是下划线命名法(如 login_name),JPA会自动根据java属性名的驼峰命名法映射成数据库表字段的下划线命名法,如 loginName 属性映射到数据库就是 login_name 字段。

这个 User 实体类写好后,我们再运行下之前的 main 程序,然后惊奇的发现:数据库里自动建了一个名为 "t_user"的表:



表示JPA在启动时根据实体类,自动在数据库中创建了对应的表了。

注意: 实体类 User 一定要放在 HelloJPAApp 类所在包中或子包中,不然
HelloJPAApp 启动时 Spring Boot 可能扫描不到。

编写 Repository 接口

有了表之后,我们要写对表进行增删改查的代码,用JPA干这事简直是简单到姥姥家了,只需要继续一个接口就搞定了,请看代码:

package com.terran4j.springboot.jpa;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {

}

这样就写完了基本的增删改查的代码? 的确是这样,因为 JpaRepository 接口已经定义了很多方法,JpaRepository 的父类也定义了很多方法,如:



而 Spring Boot JPA又帮你实现了这些方法,你只需要在继承 JpaRepository 时指定了实体类的类对象和 ID 属性的类对象就可以了,如
public interface UserRepository extends JpaRepository<User, Long>
表示实体类是 User, User 中 ID 属性是 Long 类型的。
并且, Spring Boot JPA 扫描到 UserRepository 是 Repository 的子类后,会以动态代理的方式对 UserRepository 进行实现,并将实现的对象作为 Bean 注入到 Spring 容器中,而我们只需要像使用普通的 Spring Bean 一样,用 @Autowired 引入即可使用。

下面,我们编写一个 Controller 类来调用 UserRepository ,如下所示:

package com.terran4j.springboot.jpa;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@Autowired
private HelloService helloService;

@Autowired
private UserRepository userRepository;

// URL示例: http://localhost:8080/hello/1 @RequestMapping(value = "/hello/{id}", method = RequestMethod.GET)
public String sayHello(@PathVariable("id") Long id) {

User user = userRepository.findOne(id);

if (user == null) {
return String.format("User NOT Found: %d", id);
}

String name = user.getLoginName();
return helloService.hello(name);
}

}

相关的 HelloService 的代码为:

package com.terran4j.springboot.jpa;

import org.springframework.stereotype.Component;

@Component
public class HelloService {

public String hello(String name) {
return "Hello, " + name + "!";
}

}

代码中,
User user = userRepository.findOne(id);
表示根据 id 从表中查出一条记录,并映射成 User 对象。

为了测试效果,我们先执行以下SQL在数据库中制造点数据:

delete from `t_user`;
insert into `t_user` (`id`, `login_name`, `age`, `regist_time`, `status`) values
('1','Jim','12','2017-07-26 09:29:47','enable'),
('2','Mike','23','2017-07-25 09:30:54','disable');

然后启动程序,在浏览器中用以下URL访问:

http://localhost:8080/hello/1




可以看到,
userRepository.findOne(id)
的确把数据给查出来了。

使用原生SQL查询

然而,JpaRepository 提供的仅是简单的增删查改方法,那遇到复杂的查询怎么办?
Spring Boot JAP 底层是 Hibernate 实现的, Hibernate 提供了 hql 的类SQL语法来编写复杂查询,不过我个人不建议用 HQL,因为毕竟 HQL 与SQL还是有较大不同的,需要学习成本(但这个成本其实是没必要投入的),另外就是一些场景下需要用数据库的特定优化机制时,HQL 实现不了。
所以,我的建议是使用原生 SQL 的方式实现,而 JPA 是提供了这个能力的,下面我介绍一种用在 orm.xml 中写原生 SQL的方法。

假如需求是这样的,我们要查询某一年龄段的 User(如 10岁 ~ 20岁的),SQL大概要这样写:

SELECT * FROM t_user AS u
WHERE u.age >= '10' AND u.age <= '20'
ORDER BY u.regist_time DESC

Spring Boot JAP 约定是把 SQL 写在类路径的 META-INF/orm.xml 文件中(如果 META-INF 文件夹没有就创建一个),文件路径如下:



orm.xml 文件的内容如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_0.xsd" version="2.1">

<named-native-query name="User.findByAgeRange"
result-class="com.terran4j.springboot.jpa.User">
<query><![CDATA[
select * from t_user as u
where u.age >= :minAge and u.age <= :maxAge
order by u.regist_time desc
]]></query>
</named-native-query>

</entity-mappings>

<named-native-query>
表示是一个“原生SQL查询”,
name="User.findByAgeRange"
表示给这个查询起了一个名字叫“User.findByAgeRange”,后面代码中会根据这个名字来引用这个查询,
result-class="com.terran4j.springboot.jpa.User"
表示SQL查询返回的结果集,每条记录转化成 User 对象。
<query>
里面是原生的SQL语句,其中 : 开始的是变量,如上面的SQL,有两个变量 :minAge 和 :maxAge ,这些变量的值,会从外面传入进来。

然后我们可以在 UserRepository 中添加一个
findByAgeRange
方法来使用这个原生SQL查询,如下代码所示:

package com.terran4j.springboot.jpa;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface UserRepository extends JpaRepository<User, Long> {

/**
* 查询某年龄段范围之内的用户。
*
* @param minAge
*            最小年龄。
* @param maxAge
*            最大年龄。
* @return
*/
@Query(nativeQuery = true, name = "User.findByAgeRange")
List<User> findByAgeRange(@Param("minAge") int minAge, @Param("maxAge") int maxAge);

}

这个
findByAgeRange
方法上面有一个
@Query(nativeQuery = true, name = "User.findByAgeRange")
注解,表示这个方法的实现使用名为
User.findByAgeRange
的查询,此查询是用原生SQL写的;方法参数上有
@Param
注解,表示将方法的参数值映射到查询中的变量。

最后,我们写一个 Controller 调用这个方法试,如下代码所示:

package com.terran4j.springboot.jpa;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController2 {

@Autowired
private UserRepository userRepository;

// URL示例: http://localhost:8080/users @RequestMapping(value = "/users", method = RequestMethod.GET)
@ResponseBody
public List<User> findByAgeRange(
@RequestParam(value = "minAge", defaultValue = "1") int minAge,
@RequestParam(value = "maxAge", defaultValue = "999") int maxAge) {

List<User> users = userRepository.findByAgeRange(minAge, maxAge);

return users;
}

}

然后访问 URL:
http://localhost:8080/users
,运行效果如下:



我们看到
findByAgeRange
方法把数据给查出来了,同时控制台有一行输出:

Hibernate: select * from t_user as u where u.age >= ? and u.age <= ? order by u.regist_time desc

这也是 JPA 底层实际执行的 SQL,也就是把我们写的 SQL 中 :minAge 和 :maxAge 两个变量换成“绑定变量”的方式。

总结说明

本文我们讲解了用 Spring Boot JPA 来访问数据库,是不是觉得用 Spring Boot 开发超级爽呢,本系列这三章就讲到这了,主要是带大家对 Spring Boot 快速上手,后面笔者会努力出更多关于 Spring Boot && Spring Cloud 的技术文章,敬请期待。

点击 这里 可以查看本系列的全部章节。
(本系列的目标是帮助有 Java 开发经验的程序员们快速掌握使用 Spring Boot 开发的基本技巧,感受到 Spring Boot 的极简开发风格及超爽编程体验。)

另外,我们有一个名为 SpringBoot及微服务 的微信公众号,感兴趣的同学请扫描下面的二维码关注下吧,关注后就可以收到我们定期分享的技术干货哦!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息