|
| 1 | +--- |
| 2 | +title: Springboot 系列(十)使用 Spring data jpa 访问数据库 |
| 3 | +toc_number: false |
| 4 | +date: 2019-03-01 01:40:01 |
| 5 | +url: springboot/springboot-10-data-jpa |
| 6 | +tags: |
| 7 | + - Springboot |
| 8 | + - SpringData Jpa |
| 9 | +categories: |
| 10 | + - Springboot |
| 11 | +--- |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +## 前言 |
| 16 | +Springboot data jpa 和 Spring jdbc 同属于 Spring开源组织,在 Spring jdbc 之后又开发了持久层框架,很明显 Spring data jpa 相对于 Spring jdbc 更加的便捷强大,不然也就没有开发的必要了。根据下面的文章开始体验下 Spring data jpa 魅力。 |
| 17 | +<!-- more --> |
| 18 | +## 1. Spring data jpa 介绍 |
| 19 | +Spring data jpa 是 Spring data 系列的一部分,使用它可以轻松的实现对数据访问层的增强支持,在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦,需要编写大量的样板式的代码来执行简单查询或者分页操作。Spring data jpa 的目标是尽量的减少实际编码来改善数据访问层的操作。 |
| 20 | +## 2. Spring data jpa 依赖 |
| 21 | +这次的实验基于系列文章第九篇实验代码,代码中的数据源相关的配置也可以参考系列文章第九篇,这里只演示 Spring data jpa 部分。 |
| 22 | + |
| 23 | +创建Spring boot 项目,引入需要的依赖。 |
| 24 | +```xml |
| 25 | + <dependencies> |
| 26 | + <!-- Spring Boot web 开发整合 --> |
| 27 | + <dependency> |
| 28 | + <groupId>org.springframework.boot</groupId> |
| 29 | + <artifactId>spring-boot-starter-web</artifactId> |
| 30 | + <exclusions> |
| 31 | + <exclusion> |
| 32 | + <artifactId>spring-boot-starter-json</artifactId> |
| 33 | + <groupId>org.springframework.boot</groupId> |
| 34 | + </exclusion> |
| 35 | + </exclusions> |
| 36 | + </dependency> |
| 37 | + |
| 38 | + <dependency> |
| 39 | + <groupId>org.springframework.boot</groupId> |
| 40 | + <artifactId>spring-boot-starter-test</artifactId> |
| 41 | + <scope>test</scope> |
| 42 | + </dependency> |
| 43 | + |
| 44 | + <!-- 阿里 fastjson --> |
| 45 | + <dependency> |
| 46 | + <groupId>com.alibaba</groupId> |
| 47 | + <artifactId>fastjson</artifactId> |
| 48 | + <version>1.2.47</version> |
| 49 | + </dependency> |
| 50 | + |
| 51 | + <!-- Lombok 工具 --> |
| 52 | + <dependency> |
| 53 | + <groupId>org.projectlombok</groupId> |
| 54 | + <artifactId>lombok</artifactId> |
| 55 | + <optional>true</optional> |
| 56 | + </dependency> |
| 57 | + |
| 58 | + <!-- 导入配置文件处理器,在配置springboot相关文件时候会有提示 --> |
| 59 | + <dependency> |
| 60 | + <groupId>org.springframework.boot</groupId> |
| 61 | + <artifactId>spring-boot-configuration-processor</artifactId> |
| 62 | + <optional>true</optional> |
| 63 | + </dependency> |
| 64 | + |
| 65 | + <!-- 单元测试 --> |
| 66 | + <dependency> |
| 67 | + <groupId>org.junit.jupiter</groupId> |
| 68 | + <artifactId>junit-jupiter-api</artifactId> |
| 69 | + <version>RELEASE</version> |
| 70 | + <scope>compile</scope> |
| 71 | + </dependency> |
| 72 | + |
| 73 | + <!-- 数据库访问 JPA--> |
| 74 | + <dependency> |
| 75 | + <groupId>org.springframework.boot</groupId> |
| 76 | + <artifactId>spring-boot-starter-data-jpa</artifactId> |
| 77 | + </dependency> |
| 78 | + |
| 79 | + <!--添加数据库链接 --> |
| 80 | + <dependency> |
| 81 | + <groupId>mysql</groupId> |
| 82 | + <artifactId>mysql-connector-java</artifactId> |
| 83 | + </dependency> |
| 84 | + |
| 85 | + <!-- 阿里 druid 数据源,Spring boot 中使用Druid要用这个 --> |
| 86 | + <dependency> |
| 87 | + <groupId>com.alibaba</groupId> |
| 88 | + <artifactId>druid-spring-boot-starter</artifactId> |
| 89 | + <version>1.1.10</version> |
| 90 | + </dependency> |
| 91 | + </dependencies> |
| 92 | +``` |
| 93 | +## 3. Spring data jpa 配置 |
| 94 | +关于 Druid 数据源的配置不再说明,可以参考系列文章第九篇。 |
| 95 | +```properties |
| 96 | +############################################################ |
| 97 | +# 服务启动端口号 |
| 98 | +server.port=8080 |
| 99 | +spring.profiles.active=dev |
| 100 | +############################################################ |
| 101 | +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8&serverTimezone=GMT%2B8 |
| 102 | +spring.datasource.driver-class-name= com.mysql.jdbc.Driver |
| 103 | +spring.datasource.username=root |
| 104 | +spring.datasource.password=123 |
| 105 | +# 使用 druid 数据源 |
| 106 | +spring.datasource.type: com.alibaba.druid.pool.DruidDataSource |
| 107 | +spring.datasource.initialSize: 5 |
| 108 | +spring.datasource.minIdle: 5 |
| 109 | +spring.datasource.maxActive: 20 |
| 110 | +spring.datasource.maxWait: 60000 |
| 111 | +spring.datasource.timeBetweenEvictionRunsMillis: 60000 |
| 112 | +spring.datasource.minEvictableIdleTimeMillis: 300000 |
| 113 | +spring.datasource.validationQuery: SELECT 1 FROM DUAL |
| 114 | +spring.datasource.testWhileIdle: true |
| 115 | +spring.datasource.testOnBorrow: false |
| 116 | +spring.datasource.testOnReturn: false |
| 117 | +spring.datasource.poolPreparedStatements: true |
| 118 | +spring.datasource.filters: stat |
| 119 | +spring.datasource.maxPoolPreparedStatementPerConnectionSize: 20 |
| 120 | +spring.datasource.useGlobalDataSourceStat: true |
| 121 | +spring.datasource.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 |
| 122 | +# SpringBoot JPA |
| 123 | +spring.jpa.show-sql=true |
| 124 | +# create 每次都重新创建表,update,表若存在则不重建 |
| 125 | +spring.jpa.hibernate.ddl-auto=update |
| 126 | +spring.jpa.database-platform=org.hibernate.dialect.MySQL55Dialect |
| 127 | +``` |
| 128 | +`spring.jpa.show-sql=true` 打印 SQL 语句。 |
| 129 | +`spring.jpa.hibernate.ddl-auto=update` 根据 Enity 自动创建数据表,Update 表示如果表存在则不重新创建。 |
| 130 | + |
| 131 | +## 4. Spring data jpa 编码 |
| 132 | +Springboot Data JPA 是 ORM 的完整实现,实体类和数据表关系一一对应,因此实体类也就是数据表结构。`spring.jpa.hibernate.ddl-auto=update` 会在 JPA 运行时自动在数据表中创建被 `@Entity` 注解的实体数据表。如果表已经存在,则不会创建。 |
| 133 | +### 4.1. 数据实体类 |
| 134 | +```java |
| 135 | +import lombok.AllArgsConstructor; |
| 136 | +import lombok.Data; |
| 137 | +import lombok.NoArgsConstructor; |
| 138 | +import org.springframework.format.annotation.DateTimeFormat; |
| 139 | + |
| 140 | +import javax.persistence.*; |
| 141 | +import javax.validation.constraints.NotNull; |
| 142 | +import java.util.Date; |
| 143 | + |
| 144 | +/** |
| 145 | + * <p> |
| 146 | + * |
| 147 | + * @Entity JPA实体 |
| 148 | + * @Data GET SET TOSTRING |
| 149 | + * @NoArgsConstructor 无参构造 |
| 150 | + * @AllArgsConstructor 全参构造 |
| 151 | + * @Author niujinpeng |
| 152 | + * @Date 2018/12/19 17:13 |
| 153 | + */ |
| 154 | +@Data |
| 155 | +@NoArgsConstructor |
| 156 | +@AllArgsConstructor |
| 157 | +@Entity |
| 158 | +@Table(name = "user") |
| 159 | +public class User { |
| 160 | + |
| 161 | + /** |
| 162 | + * 用户ID |
| 163 | + * |
| 164 | + * @Id 主键 |
| 165 | + * @GeneratedValue 自增主键 |
| 166 | + */ |
| 167 | + @Id |
| 168 | + @GeneratedValue(strategy = GenerationType.IDENTITY) |
| 169 | + private Integer id; |
| 170 | + |
| 171 | + /** |
| 172 | + * 用户名 |
| 173 | + */ |
| 174 | + @Column(name = "username", length = 32, nullable = false) |
| 175 | + @NotNull(message = "用户名不能为空") |
| 176 | + private String username; |
| 177 | + /** |
| 178 | + * 密码 |
| 179 | + */ |
| 180 | + @Column(name = "password", length = 32, nullable = false) |
| 181 | + @NotNull(message = "密码不能为空") |
| 182 | + private String password; |
| 183 | + /** |
| 184 | + * 年龄 |
| 185 | + */ |
| 186 | + @Column(name = "age", length = 3) |
| 187 | + private Integer age; |
| 188 | + /** |
| 189 | + * 生日 |
| 190 | + */ |
| 191 | + @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") |
| 192 | + private Date birthday; |
| 193 | + /** |
| 194 | + * 技能 |
| 195 | + */ |
| 196 | + private String skills; |
| 197 | +} |
| 198 | +``` |
| 199 | + |
| 200 | +### 4.2. JPA 操作接口 |
| 201 | +JPA 操作接口只需要继承 JpaRepository 就可以了,JpaRepository 里封装了常用的增删改查分页等方法,可以直接使用,如果需要自定义查询方式,可以通过构造方法名的方式增加。下面增加了一个根据 username 和 password 查询 User 信息的方法。 |
| 202 | + |
| 203 | +```java |
| 204 | +package net.codingme.boot.domain.repository; |
| 205 | + |
| 206 | +import net.codingme.boot.domain.User; |
| 207 | +import org.springframework.data.jpa.repository.JpaRepository; |
| 208 | +import org.springframework.stereotype.Repository; |
| 209 | + |
| 210 | +/** |
| 211 | + * <p> |
| 212 | + * |
| 213 | + * @Author niujinpeng |
| 214 | + * @Date 2019/1/1114:26 |
| 215 | + */ |
| 216 | +@Repository |
| 217 | +public interface UserRepository extends JpaRepository<User, Integer> { |
| 218 | + /** |
| 219 | + * 一个自定义方法,根据 username 和 password 查询 User 信息 |
| 220 | + */ |
| 221 | + User findByUsernameAndPassword(String username, String password); |
| 222 | +} |
| 223 | +``` |
| 224 | +到这里,Jpa 的功能已经可以测试使用了,关于 Service 层和 Controller 就不在这里贴了,直接编写 Springboot 单元测试进行 Jpa 测试。 |
| 225 | + |
| 226 | +## 5. Spring data jpa 测试 |
| 227 | +使用 Springboot 的单元测试方法可以方便的测试 Springboot 项目,对 Springboot 单元测试不了解的可以直接参照[官方文档](https://docs.spring.io/spring-boot/docs/2.1.x/reference/htmlsingle/#boot-features-testing-spring-applications)的说明,当然,也可以直接看下面的示例代码。 |
| 228 | +下面编写四个测试方法分别测试根据 Id 查询、分页查询、更新数据、根据 username 和 password 查询四个功能。 |
| 229 | +```java |
| 230 | +package net.codingme.boot.domain.repository; |
| 231 | + |
| 232 | +import net.codingme.boot.domain.User; |
| 233 | +import org.junit.Assert; |
| 234 | +import org.junit.Test; |
| 235 | +import org.junit.runner.RunWith; |
| 236 | +import org.springframework.beans.factory.annotation.Autowired; |
| 237 | +import org.springframework.boot.test.context.SpringBootTest; |
| 238 | +import org.springframework.data.domain.Page; |
| 239 | +import org.springframework.data.domain.PageRequest; |
| 240 | +import org.springframework.test.context.junit4.SpringRunner; |
| 241 | +import java.util.List; |
| 242 | +import java.util.Optional; |
| 243 | + |
| 244 | +/** |
| 245 | + * 单元测试 |
| 246 | + */ |
| 247 | +@RunWith(SpringRunner.class) |
| 248 | +@SpringBootTest |
| 249 | +public class UserRepositoryTest { |
| 250 | + |
| 251 | + @Autowired |
| 252 | + private UserRepository userRepository; |
| 253 | + |
| 254 | + /** |
| 255 | + * id查询 |
| 256 | + */ |
| 257 | + @Test |
| 258 | + public void findByIdUserTest() { |
| 259 | + Optional<User> userOptional = userRepository.findById(1); |
| 260 | + User user = userOptional.orElseGet(null); |
| 261 | + System.out.println(user); |
| 262 | + Assert.assertNotNull(user); |
| 263 | + } |
| 264 | + |
| 265 | + /** |
| 266 | + * 分页查询 |
| 267 | + */ |
| 268 | + @Test |
| 269 | + public void findByPageTest() { |
| 270 | + PageRequest pageRequest = PageRequest.of(0, 2); |
| 271 | + Page<User> userPage = userRepository.findAll(pageRequest); |
| 272 | + List<User> userList = userPage.getContent(); |
| 273 | + userList.forEach((user) -> System.out.println(user)); |
| 274 | + Assert.assertNotNull(userList); |
| 275 | + } |
| 276 | + |
| 277 | + /** |
| 278 | + * 更新 |
| 279 | + */ |
| 280 | + @Test |
| 281 | + public void updateUserTest() { |
| 282 | + Optional<User> userOptional = userRepository.findById(1); |
| 283 | + User user = userOptional.orElseThrow(() -> new RuntimeException("用户信息没有取到")); |
| 284 | + System.out.println(user.getAge()); |
| 285 | + ; |
| 286 | + user.setAge(user.getAge() + 1); |
| 287 | + User updateResult = userRepository.save(user); |
| 288 | + Assert.assertNotNull(updateResult); |
| 289 | + } |
| 290 | + |
| 291 | + /** |
| 292 | + * 根据 Username 和 Password 查询 |
| 293 | + */ |
| 294 | + @Test |
| 295 | + public void findByUsernameAndPasswordTest() { |
| 296 | + User user = userRepository.findByUsernameAndPassword("Darcy", "123"); |
| 297 | + System.out.println(user); |
| 298 | + Assert.assertNotNull(user); |
| 299 | + } |
| 300 | +} |
| 301 | +``` |
| 302 | +首先看到四个方法全部运行通过。 |
| 303 | + |
| 304 | +分页查询查出数据库中的两条数据。 |
| 305 | +```log |
| 306 | +Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.birthday as birthday3_0_, user0_.password as password4_0_, user0_.skills as skills5_0_, user0_.username as username6_0_ from user user0_ limit ? |
| 307 | +Hibernate: select count(user0_.id) as col_0_0_ from user user0_ |
| 308 | +User(id=1, username=Darcy, password=123, age=18, birthday=2019-01-12 21:02:30.0, skills=Go) |
| 309 | +User(id=3, username=Chris, password=456, age=23, birthday=2019-01-01 00:11:22.0, skills=Java) |
| 310 | +``` |
| 311 | +根据 Id 查询也没有问题。 |
| 312 | +``` |
| 313 | +Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.birthday as birthday3_0_0_, user0_.password as password4_0_0_, user0_.skills as skills5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=? |
| 314 | +User(id=1, username=Darcy, password=123, age=18, birthday=2019-01-12 21:02:30.0, skills=Go) |
| 315 | +``` |
| 316 | +更新操作也是正常输出 SQL ,没有任何异常。 |
| 317 | +``` |
| 318 | +Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.birthday as birthday3_0_0_, user0_.password as password4_0_0_, user0_.skills as skills5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=? |
| 319 | +18 |
| 320 | +Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.birthday as birthday3_0_0_, user0_.password as password4_0_0_, user0_.skills as skills5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=? |
| 321 | +Hibernate: update user set age=?, birthday=?, password=?, skills=?, username=? where id=? |
| 322 | +``` |
| 323 | +最后一个是自定义查询操作,上面三个方法的输出中,Darcy 用户对应的年龄是 18,在经过更新加1 之后应该变为19,下面是自定义查询的结果。 |
| 324 | +``` |
| 325 | +Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.birthday as birthday3_0_, user0_.password as password4_0_, user0_.skills as skills5_0_, user0_.username as username6_0_ from user user0_ where user0_.username=? and user0_.password=? |
| 326 | +User(id=1, username=Darcy, password=123, age=19, birthday=2019-01-12 21:02:30.0, skills=Go) |
| 327 | +``` |
| 328 | +可见是没有任何问题的。 |
| 329 | +文章代码已经上传到 [GitHub](https://github.com/niumoo/springboot/tree/master/springboot-data-jpa)。 |
| 330 | +测试代码中使用了一些 JDK8 的特性,如 `Optional` 类的使用,以后会单独写一部分关于 JDK 新特性的文章,欢迎扫码关注公众号。 |
0 commit comments