SpringBoot 与 jpa 的简单使用 一. 简述 jpa
全称 javax.persistence.annotation
,刚开始是由 hibernate
作者开发,后面被 java
公司收入到规范之中。spring-boot-data-jpa
就是在这套规范上面建立起来的。所以开发的时候默认的 orm
框架就是 hibernate
。 在刚开始接触编程的时候,万事以快为主,但其实现在我感觉开发体验更加重要。mybatis
是个封装比较少的框架,速度会略胜一筹,但 hibernate
开发数据库的时候会显得更加面向对象。当然两者都可以在不同的业务需求中体现其重要性。 简单的开发区别就是,把 hibernate
框架的 repository
当成一个集合来使用,使用起来就更加的得心应手。mybatis
则更加的面向过程,自主控制 SQL
的运行。 GitHub地址:https://github.com/WeidanLi/spring-boot-tutorial 演示项目:spring-boot-data-jpa
二. 开发 我们就通过一个简单用户系统来开发
1. mvn 依赖 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 <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> <!-- mysql依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 为了方便测试加入端口检测包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- SpringBoot 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 为了测试的时候使用内存数据库 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> </dependencies>
2. 开发用户接口 此处通常需要返回 DTO
类,但是为了方便就不去做转换了。
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("user") public class UserEndpoint { private UserService userService; @Autowired public UserEndpoint (UserService userService) { this .userService = userService; } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void add (@RequestBody UserDo userDo) { userService.add(userDo); } @PutMapping @ResponseStatus(HttpStatus.NO_CONTENT) public void update (@RequestBody UserDo userDo) { userService.update(userDo); } @DeleteMapping("{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void delete (@PathVariable("id") Long userId) { userService.delete(userId); } @GetMapping("{id}") public UserDo getById (@PathVariable("id") Long userId) { return userService.getById(userId); } }
3. 用户业务层 关于为什么使用业务层做示例呢,因为直接接口请求仓库没有事务边界,在使用 update
相关方法的时候,并不能帮我自动刷新到数据库
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 @Service @Transactional public class UserService { private UserRepository userRepository; @Autowired public UserService (UserRepository userRepository) { this .userRepository = userRepository; } public void add (UserDo userDo) { userRepository.save(userDo); } public void update (UserDo userDo) { Optional<UserDo> origin = userRepository.findById(userDo.getId()); origin.ifPresent(originUserDo -> originUserDo.setName(userDo.getName())); } public void delete (Long userId) { userRepository.deleteById(userId); } @Transactional(readOnly = true) public UserDo getById (Long userId) { return userRepository.findById(userId) .orElse(null ); } }
4. 用户DBO类 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 @Entity @Table(name = "user_db") public class UserDo { @Id @Column(name = "user_id") @GeneratedValue(strategy= GenerationType.IDENTITY) private Long id; @Column(name = "user_name", length = 50) private String name; public Long getId () { return id; } public void setId (Long id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public String toString () { return "UserDo{" + "id=" + id + ", name='" + name + '\'' + '}' ; } }
5. 用户仓库类 只需要通过继承接口的方式去获取他应有的方法
1 2 3 @Repository public interface UserRepository extends JpaRepository <UserDo, Long> {}
JpaRepository
接口的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface JpaRepository <T, ID> extends PagingAndSortingRepository <T, ID>, QueryByExampleExecutor<T> { List<T> findAll () ; List<T> findAll (Sort var1) ; List<T> findAllById (Iterable<ID> var1) ; <S extends T > List<S> saveAll (Iterable<S> var1) ; void flush () ; <S extends T > S saveAndFlush (S var1) ; void deleteInBatch (Iterable<T> var1) ; void deleteAllInBatch () ; T getOne (ID var1) ; <S extends T > List<S> findAll (Example<S> var1) ; <S extends T > List<S> findAll (Example<S> var1, Sort var2) ; }
6. 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 spring: application: name: spring-boot-data-jpa jpa: hibernate: ddl-auto: create-drop show-sql: true database-platform: org.hibernate.dialect.MySQL5InnoDBDialect datasource: username: root password: root driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/jpa_data?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
7. 请求接口 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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 POST http://127.0.0.1:8080/user HTTP/1.1 201 Content-Length: 0 Date: Wed, 19 Dec 2018 02:42:16 GMT <Response body is empty> Response code: 201; Time: 70ms; Content length: 0 bytes ------------------------------------------------------- GET http://127.0.0.1:8080/user/1 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 19 Dec 2018 02:42:41 GMT { "id": 1, "name": "狗娃" } Response code: 200; Time: 31ms; Content length: 20 bytes ------------------------------------------------------- PUT http://127.0.0.1:8080/user Content-Type: application/json { "id": 1, "name": "狗娃" } HTTP/1.1 204 Date: Wed, 19 Dec 2018 02:43:06 GMT <Response body is empty> Response code: 204; Time: 21ms; Content length: 0 bytes ------------------------------------------------------- GET http://127.0.0.1:8080/user/1 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 19 Dec 2018 02:43:24 GMT { "id": 1, "name": "狗娃" } Response code: 200; Time: 39ms; Content length: 20 bytes ------------------------------------------------------- DELETE http://127.0.0.1:8080/user/1 HTTP/1.1 204 Date: Wed, 19 Dec 2018 02:49:49 GMT <Response body is empty> Response code: 204; Time: 128ms; Content length: 0 bytes
8. 测试业务层 通常来说,按照 TDD
的开发模式,都需要有测试用例,在日常开发中我习惯测试业务层(没那么多时间覆盖所有测试)。所以现在按照测试业务层做示例。 测试用例可以使用 MySQL
来做也可以用内存数据库来做,我演示是使用内存数据库来做的。
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @SpringBootTest @RunWith(SpringRunner.class) @Transactional public class UserServiceTest { @Autowired private UserService userService; @Autowired private UserRepository userRepository; @Test public void add () { UserDo userDo = new UserDo (); userDo.setName("狗蛋" ); userService.add(userDo); long count = userRepository.findAll().stream() .filter(userDo1 -> userDo1.getName().equals(userDo.getName())) .count(); assertTrue("should have a user who name is " + userDo.getName(), count > 0 ); } @Test public void update () { UserDo userDo = new UserDo (); userDo.setName("狗剩" ); userService.add(userDo); Optional<UserDo> optionalUserDo = userRepository.findAll() .stream().findAny(); optionalUserDo.ifPresent(userDo1 -> { UserDo update = new UserDo (); update.setId(userDo1.getId()); update.setName("狗仔" ); userService.update(update); }); Optional<UserDo> aDo = userRepository.findAll() .stream().filter(query -> query.getName().equals("狗仔" )).findAny(); assertTrue("should have a user who name is 狗仔" , aDo.isPresent()); } @Test public void delete () { UserDo userDo = new UserDo (); userDo.setName("狗剩" ); userService.add(userDo); Optional<UserDo> optionalUserDo = userRepository.findAll() .stream().findAny(); Long id = optionalUserDo.get().getId(); userService.delete(id); long count = userRepository.findAll() .stream().filter(userDo1 -> userDo1.getId().equals(id)) .count(); assertTrue("should not have a user who id is " + id, count < 1 ); } @Test public void getById () { UserDo userDo = new UserDo (); userDo.setName("狗剩" ); userService.add(userDo); Optional<UserDo> optionalUserDo = userRepository.findAll() .stream().findAny(); UserDo byId = userService.getById(optionalUserDo.get().getId()); assertNotNull("result should not be null" , byId); } }
test/resources
放置 Spring-Boot
的配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: application: name: spring-boot-data-jpa jpa: hibernate: ddl-auto: create-drop show-sql: true database-platform: org.hibernate.dialect.MySQL5InnoDBDialect datasource: username: root password: root driver-class-name: org.h2.Driver url: jdbc:h2:mem:scope_test_db;MODE=MYSQL;DB_CLOSE_DELAY=-1
OK,运行完整测试即可。
三. 总结 使用 spring-boot-data-jpa
应该尽量避免使用 SQL
来编写,会出现一些不可控的问题。尽量使用 jpa
提供的接口来做增删查改操作,后期迁移数据库的时候也不会显得无力(当然这种事情很少发生)。