escapar CodingDaydreaming
Web应用一把梭:CRUD前篇
Spring Boot, Java, Web, JPA, Hibernate, CRUD, Swagger
本篇基于上一篇的实例,将Mock的数据源改为真实数据库。 CRUD是增删改查的简写。 前篇只实现单一Model的CRUD,并完成Swagger的集成,方便今后测试。 前篇和后篇的代码都在:https://github.com/POJOa/demo-app-2nd ## 准备工作 请先安装MySQL,过程略。 ### 修改Spring Boot全局配置文件 根据之前的数据库配置修改/src/main/resrouces/application.properties 。 ``` java spring.datasource.url=jdbc:mysql://localhost:3306/demo_app?useUnicode=true&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=qwerty spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true #spring.jpa.hibernate.ddl-auto=create #logging.level.org.hibernate=DEBUG ``` spring.datasource.url 中的 demo_app 是Schema(database)的名字,spring.datasource.username 和 password顾名思义。spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true 可以暂时不加,如果今后在懒加载的序列化上出现问题,可以试着加这条,原理网上有很多说明。 如果不想导入我提供的sql文件,可以取消#spring.jpa.hibernate.ddl-auto=create的注释,Hibernate会根据Domain Model的定义生成表结构。 ### 导入依赖包 修改/pom.xml 在`<dependencies>`标签内加入 ``` html <!-- NEW DEPENDENCIES ADDED START--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>LATEST</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>LATEST</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> <!-- NEW DEPENDENCIES ADDED END--> ``` ### 配置Swagger Swagger是一个非常实用的测试和文档生成库,但和Spring Boot的集成“Spring Fox”似乎不太稳定,升级经常break东西,考虑到它需要兼顾的复杂情况,也许也不奇怪了。如果遇到了问题,你可能需要在swagger和spring fox等多个repo之间辗转查询,请抱有耐心。所以最好谨慎升级,目前使用2.8.0。以下是最基础的配置,更多的玩法,请参看[Spring Fox的文档](https://springfox.github.io/springfox/docs/current/)。 ``` java @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example.demo.interfaces")) .paths(PathSelectors.any()) .build(); } } ``` ## 将Domain Model改为Hibernate持久化类 本系列文章采用Spring Data JPA作为持久化方案,其中JPA的标准由Hibernate实现。 Hibernate持久化类必须包含对表结构的映射。 以Category(类别) Domain为例子: ``` java @Getter @Setter @Accessors(chain = true) @Entity @Table(name="category") @NamedQuery(name="Category.findAll", query="SELECT c FROM Category c") public class Category { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private long id; @Column(name="name") private String name; } ``` 类的前三个Annotation是Lombok的功能,它可以帮助我们生成Fluent API的Getter和Setter,维持代码简洁。如果觉得不放心,也可以不用它,自己生成。 类的后三个Annotation必不可少,依据惯例,参数就是这些内容。字面意思很明确,详细意义超出了这个系列文章的讨论范围,为了面试着想, **最好了解一下**。 在Id前必须有这两个Annotation,当然可以有不同的strategy,这里依据惯例就用自增的long ID。建议打上@Column annotation,如果名字一样,其实是可以缺省的。 ## 创建和Domain Model相符的DTO 创建一个CategoryDTO类,用于和前端交互,隔离持久化类和序列化工具,以免出现性能和安全问题。 ``` java @Getter @Setter @Accessors(chain = true) public class CategoryDTO { private long id; private String name; } ``` 为了让Mapping工具在默认模式下工作正常,类的变量名字要尽可能保持一致。 ## 改造Domain的Repository 之前我们的Repo的数据是Mock的,这回要改成“正版”的了,事实上Spring Data JPA对基础的CRUD(增删改查)已经有一个默认的实现。 ``` java @Repository public interface CategoryRepo extends CrudRepository<Category, Long> { public Category findByNameIgnoreCase(String name); } ``` 如以上的代码,Repository **是一个接口**而不是像之前Mock的一样,是一个类。 CrudRepository是泛型接口,泛型接口的标示,前一个是Repo对应的持久化类,后一个是持久化类Id的数据类型,请用包装类。 Spring Data JPA有一个根据接口方法名生成查询的方式,这是个非常有趣的特性,可以在[官方文档里查到所有的用法](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/)。比如它以上的方法就是根据name查询Category并忽略大小写,在处理简单的查询的时候还是很顺手的,复杂的查询会导致方法名超长,建议用@Query自定义SQL或HQL(暂不涉及)。 ## 改造Domain对应的Interface ``` java @RestController @RequestMapping(value = "api/category/") public class CategoryAPI { private static ModelMapper m = new ModelMapper(); @Autowired CategoryService service; @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json") public Category get(@PathVariable Long id) { return service.get(id); } @RequestMapping(value = "/", method = RequestMethod.GET, produces = "application/json") public Iterable<Category> list() { return service.findAll(); } @RequestMapping(value = "/", method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public CategoryDTO save(@RequestBody CategoryDTO dto) { Category c = m.map(dto,Category.class); return m.map(service.save(c),CategoryDTO.class); } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json") public void delete(@PathVariable Long id) { service.delete(id); } } ``` 这是一个最基本的CRUD API,在此列出比起上篇增加的内容: - ModelMapper ,偷懒用的,不用写从Domain到DTO的大段set语句了。 - @RequestBody 的 Annotation对于接收JSON的API千万不能遗漏 - 不要忘了指定正确的 method ## 运行并用Swagger测试RESTful API 访问`http://localhost:8080/swagger-ui.html` 展开category-api,如下图 ![](https://wx3.sinaimg.cn/large/6849f9a1ly1fow4pvzo4xj21kw0qvq91.jpg) 展开第一个GET,测试是否可用 ![](https://wx2.sinaimg.cn/mw1024/6849f9a1ly1fow4t6tr5mj21kw16ogt2.jpg) 用类似的方式可以测试所有API、指定参数。