本章是《 Spring Boot 快速入门 》系列教程的第三章,若要查看本系列的所有章节,请点击 这里 。php
在上一章《 Spring Boot MVC 》中,咱们了解了使用 Spring Boot MVC 来开发 Http Restful API的相关技术,但只处理HTTP请求是不够的,如今的应用程序大多使用了关系型数据库,所以本章咱们会带着你们继续 Spring Boot 体验之旅,此次咱们将采用 JPA 技术来访问数据库,给 Hello Spring Boot 程序添加带数据库访问演示代码。java
本章的示例代码放在“码云”上,你们能够免费下载或浏览:mysql
https://git.oschina.net/terran4j/springboot/tree/master/springboot-jpagit
相关软件使用的版本:程序员
程序在以上版本均调试过,能够正常运行,其它版本仅做参考。web
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体“对象持久化”到数据库中。 JPA技术能够极大的下降了对数据库编程的复杂性,一些简单的增删改查的操做,代码只须要操做对象就能够了,JPA自动的帮你映射成数据库的SQL操做。spring
不过 JPA 只是标准标准,而 Spring Boot 提供了它的技术实现: Spring Data JPA。不过 Spring Data JPA 也不是重复造轮子,它是基于一个很是著名的ORM框架——Hibernate——之上封装实现的。sql
Spring Data JPA 极大简化了数据库访问层代码,只要3步,就能搞定一切:数据库
spring-boot-starter-data-jpa
,及在 application配置文件中配置数据库链接。另外,若是有复杂的SQL查询,Spring Data JPA 也提供了编写原生 SQL 实现的方式。 apache
首先,咱们要在 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 可能扫描不到。
有了表以后,咱们要写对表进行增删改查的代码,用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)
的确把数据给查出来了。
然而,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及微服务 的微信公众号,感兴趣的同窗请扫描下面的二维码关注下吧,关注后就能够收到咱们按期分享的技术干货哦!