本文导读
- 关于 Spring Data、JPA、Spring Data JPA 等理论只是可以参考《Spring Data JPA 理论简介》
- Sping Data JPA 底层默认由 Hibernate 实现,同样基于 ORM(对象关系型映射),但操作比 Hibernate 更简单,以前 Hibernate 还需要程序员封装 BaseDao 层,而 Spring Data JPA 连这一步都省略了,实现接口之后,就可以直接调用其中的 CRUD 方法以及排序、分页等方法,更加简洁。
- Spring Data 提供统一的 Repository(仓库) 接口
- Repository<T, ID extends Serializable>:统一接口,根接口
- RevisionRepository<T, ID extends Serializable, N extends Number & Comparable<N>>:基于乐观锁机制
- CrudRepository<T, ID extends Serializable>:通用 CRUD 操作接口
- PagingAndSortingRepository<T, ID extends Serializable>:通用 CRUD 及分页、排序等操作操作
- JpaRepository:操作关系型数据库时,只要继承此接口,则相当于有了 CRUD、分页、排序等常用的所有方法,程序员可以直接调用。
- 为了思路清晰,本文将新建项目
环境准备
新建项目
默认 pom. xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<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>www.wmx.com</groupId>
<artifactId>seals</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>seals</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 引入 Spring Data JPA-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 引入 JDBC,只要操作数据就得有 JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- Html 的魔板引擎 Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- web应用必须引入的 web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring 官方的测试模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Data JPA CRUD
数据源配置
- 为了写起来根据简单,使用 yml 文件进行配置数据源
- 本文直接使用默认 Spring Boot 2.0.4 提供的 默认数据源 HikariDataSource ,如果需要使用 DruidDataSource 的可以参考《 Spring Boot 自定义数据源 DruidDataSource》
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/seals?characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
- 可以测试一下获取数据源是否成功
package com.lct.www;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SealsApplicationTests {
/**
* Spring Boot 默认已经配置好了数据源,程序员可以直接 DI 注入然后使用即可
*/
@Resource
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
System.out.println("数据源>>>>>>" + dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println("连接>>>>>>>>>" + connection);
System.out.println("连接地址>>>>>" + connection.getMetaData().getURL());
connection.close();
}
}
- 控制台输出如下:
数据源>>>>>>class com.zaxxer.hikari.HikariDataSource
连接>>>>>>>>>HikariProxyConnection@565077371 wrapping com.mysql.jdbc.JDBC4Connection@799f916e
连接地址>>>>>jdbc:mysql://localhost:3306/seals?characterEncoding=UTF-8
2018-08-25 11:21:01.428 INFO 17440 --- [ Thread-2] o.s.w.c.s.GenericWebApplicationContext : Closing org.springframework.web.context.support.GenericWebApplicationContext@210ab13f: startup date [Sat Aug 25 11:20:56 CST 2018]; root of context hierarchy
2018-08-25 11:21:01.433 INFO 17440 --- [ Thread-2] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2018-08-25 11:21:01.434 INFO 17440 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2018-08-25 11:21:01.441 INFO 17440 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.Process finished with exit code 0
domain Area
- 提供封装数据库表 area 的 POJO ,以及配置与数据库表的映射关系
- 因为 Spring Data JPA 也是基于 ORM 思想,而且底层又是 Hibernate 实现,所以 POJO 与数据库表的映射关系写法与以前写 Hibernate 时基本无异
- 同理可以自己在数据库手动建表,也可以通过 Hibernate 根据映射关系自动建表,本文将演示后者。
- 注意再提醒一次:所有需要 Spring Boot 自动扫描的注解必须放在应用启动类同目录下
package com.lct.www.domain;
import javax.persistence.*;
import java.util.Date;
/**
* Created by Administrator on 2018/8/25 0025.
* 区域--------实体类
*
* @Entity :告诉 JPA 本类是一个与数据库表进行映射的实体类,而不是普通的 Java Bean
* @Table :指定本映射实体类与数据库哪个表进行映射,不写时默认为类名首字母小写(area)
*/
@Entity
@Table(name = "area")
public class Area {
/**
* @Id : 指定此字段为数据库主键
* @GeneratedValue :指定主键生成的策略(strategy),可以选择如下:
* TABLE,
* SEQUENCE:由 DB2、Oracle、SAP DB 等数据库 使用自己的 序列 进行管理生成
* IDENTITY:由数据库自己进行管理,如 Mysql 的 自动递增
* AUTO:让ORM框架自动选择,默认值
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/**
* @Column :指定此属性映射数据库表的哪个字段,不写时默认为属性名
* length :指定此字段的长度,只对字符串有效,不写时默认为255
* unique :是否添加唯一约束,不写时默认为 false
* 更多详细属性,可以进入 javax.persistence.Column 查看
*/
@Column(name = "name", length = 32, unique = true)
private String name;
/**
* nullable :表示此字段是否允许为null
*/
@Column(nullable = false)
private String clients;
private Date createTime;
public String getClients() {
return clients;
}
public void setClients(String clients) {
this.clients = clients;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Area{" +
"clients='" + clients + '\'' +
", id=" + id +
", name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
自定义 Dao 接口
- 编写一个 Dao 接口(Repository)来操作实体类对应的数据表,实现了如下的接口,就拥有了其对应的方法。
- 因为使用的是 Spring Data JPA,所以直接继承 JpaRepository 接口,之后便可以支持从 service 层或者 Controller层调用其方法。
package com.lct.www.dao;
import com.lct.www.domain.Area;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by Administrator on 2018/8/25 0025.
* 自定义接口继承 JpaRepository<T, ID> 即可
* T :泛型,传入操作的实体类即可
* TD:传入实体类主键的类型
* 如果是以前 Hibernate 则还需要在 Dao 层的实现类上加 @Repository 注解注入每一个 Dao实现组件
* 而 Spring Data JPA 的数据库访问层就已经完成了,继承了JpaRepository接口,就拥有了所有的 CRUD方法、排序、分页方法
* 继承 JpaRepository 之后,本接口就已经是 JPA 的 @Repository 了,所以不要再加,直接在 service层或者controller层注入即可
*/
public interface AreaDao extends JpaRepository<Area, Integer> {
}
JPA 配置
- Spring Boot 提供了配置 JPA,如以前 Hibernate 时期的是否显示 sql 语句,对表策略等,如下所示只是写法略有不同,意义与以前 Hibernate 时完全一样。
- 如果是自己在数据库中手动建表,则省略 "ddl-auto" 的对表策略配置即可
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/seals?characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
#JPA 配置,可以参考官网:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#common-application-properties
jpa:
hibernate:
# 更新或者创建数据库表结构,省略时将不会自动生成数据库表
ddl‐auto: update
# 控制台是否显示SQL
show‐sql: true
- 完整配置选项可以参考官网:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#common-application-properties,内容如下所示:
# JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
spring.data.jpa.repositories.enabled=true # Whether to enable JPA repositories.
spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.
spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.
spring.jpa.generate-ddl=false # Whether to initialize the schema on startup.
spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Defaults to "create-drop" when using an embedded database and no schema manager was detected. Otherwise, defaults to "none".
spring.jpa.hibernate.naming.implicit-strategy= # Fully qualified name of the implicit naming strategy.
spring.jpa.hibernate.naming.physical-strategy= # Fully qualified name of the physical naming strategy.
spring.jpa.hibernate.use-new-id-generator-mappings= # Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.
spring.jpa.mapping-resources= # Mapping resources (equivalent to "mapping-file" entries in persistence.xml).
spring.jpa.open-in-view=true # Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request.
spring.jpa.properties.*= # Additional native properties to set on the JPA provider.
spring.jpa.show-sql=false # Whether to enable logging of SQL statements.
测试生成表
AreaController
- 省略 service层,直接浏览器访问控制层,然后进行 CRUD 操作,操作结果直接返回给页面,不做跳转
package com.lct.www.controllr;
import com.lct.www.dao.AreaDao;
import com.lct.www.domain.Area;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
/**
* Created by Administrator on 2018/8/25 0025.
* 区域---控制层
*/
@Controller
public class AreaController {
@Resource
private AreaDao areaDao;
/**
* 根据id 查询
*
* @param id
* @return
*/
@ResponseBody
@GetMapping("/seals/{id}")
public Area findAreaById(@PathVariable("id") Integer id, HttpServletResponse response) {
System.out.println("com.lct.www.controllr.AreaController.findAreaById:::" + id);
/**
* 推荐使用 findById 方式,而不是 getOne方法
* isPresent 判断 Optional是否为空,即有没有值
*/
Optional<Area> areaOptional = areaDao.findById(id);
if (!areaOptional.isPresent()) {
return null;
} else {
System.out.println("area2::" + areaOptional.get());
/** 获取Area值*/
return areaOptional.get();
}
}
/**
* 查询所有
*
* @return
*/
@ResponseBody
@GetMapping("/seals")
public List<Area> findAllAreas() {
List<Area> areaList = areaDao.findAll();
return areaList;
}
/**
* 添加 区域
* localhost:8080/seals/save?name=党建学习区&clients=1,2&createTime=2018/08/12
* localhost:8080/seals/save?name=一学一做区&clients=4,5,6&createTime=2018/08/15
*
* @param area
* @return 添加成功后重定向到查询所有
*/
@GetMapping("seals/save")
public String saveArea(Area area) {
/**
* save 方法:当实体的主键不存在时,则添加;实体的主键存在时,则更新
*/
areaDao.save(area);
return "redirect:/seals";
}
/**
* 更新区域
* localhost:8080/seals/save?id=2&name=一学一做区2&clients=4,5,6&createTime=2018/08/15
*
* @param area
* @return
*/
@GetMapping("/seals/update")
public String updateArea(Area area) {
/**
* save 方法:当实体的主键不存在时,则添加;实体的主键存在时,则更新
*/
areaDao.save(area);
return "redirect:/seals";
}
/**
* 根据主键 id 删除区域
*
* @return
*/
@GetMapping("/seals/del/{id}")
public String deleteAreaById(@PathVariable("id") Integer id) {
areaDao.deleteById(id);
return "redirect:/seals";
}
/**
* 删除表中所有数据
*
* @return
*/
@GetMapping("seals/delAll")
public String deleteAll() {
areaDao.deleteAll();
return "redirect:/seals";
}
/**
* 另外还有分页查询、排序查询等,在此就不再一一测试
*/
}
CRUD 测试
- 添加测试:
- 查询测试:
- 修改测试:
- 删除测试: