:) 本篇会分别介绍 Spring Data ,JPA ,Spring Data JPA
JPA简介
jpa全称是 Java Persistence API,jpa定义了各种注解(用来定义实体,映射关系)。JPA仅仅是一个规范,它的实现比较出名的是Hibernate
JPA实体
- 实体可以简单理解成一组状态的集合
- 实体需要能够持久化并有持久化标识,支持事务
- JPA中的实体使用注解@Entity标记,实体的id使用@Id标记,实体对应的数据库表名使用@Table标记,实体对应数据库字段使用@Column标记,主键生成策略使用@GeneratedValue标记
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "user")
public class User implements Serializable {
@Id
// 主键自动增长
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
// 默认对应字段 number
private String number;
// 省略 getter & setter
}
实体还需要一个默认的无参构造函数
使用spring boot简单测试jpa
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
- 在classpath:META-INF下面创建一个java persistence 的配置文件(注意文件路径必须是 META-INF/persistence.xml)
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="JPA" transaction-type="RESOURCE_LOCAL">
<!-- 配置jpa ORM产品 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 添加对应的持久化类 -->
<class>com.suse.yudapi.entity.User</class>
<properties>
<!-- jpa中连接数据库 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/demo" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="123456"></property>
<!-- jpa中配置hibernate基本属性 -->
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
- 创建一个测试程序
public class JPAMain {
public static void main(String[] args){
EntityManagerFactory factory = Persistence.createEntityManagerFactory("JPA");
EntityManager manager = factory.createEntityManager();
manager.getTransaction().begin();
User user = new User();
user.setName("yudapi");
user.setNumber("12312312");
manager.persist(user);
manager.getTransaction().commit();
}
}
JPA的CRUD操作
- CRUD都是使用的EntityManager的方法来完成的,persist,find,remove
public class JPAMain {
public static void main(String[] args){
EntityManagerFactory factory = Persistence.createEntityManagerFactory("JPA");
EntityManager manager = factory.createEntityManager();
User user = new User();
user.setName("yudapi");
user.setNumber("12312312");
// 新增
manager.getTransaction().begin();
manager.persist(user);
manager.getTransaction().commit();
// 查找
User userInfo = manager.find(User.class, 1L);
System.out.println(userInfo.getName()+" "+userInfo.getNumber());
// 更新
userInfo.setName("update after");
manager.getTransaction().begin();
manager.persist(userInfo);
manager.getTransaction().commit();
System.out.println(userInfo.getName()+" "+userInfo.getNumber());
// 删除
manager.getTransaction().begin();
manager.remove(userInfo);
manager.getTransaction().commit();
}
}
JPA中的集合映射
- 类似一对多的关系,但是没有双向关联关系,也不能级联操作。
- 新建一个Book实体,一个用户持有多个book实体,注意这个实体有一个Embeddable注解。这个注解表示这个实体可以嵌入其他实体
@Entity
@Table(name = "books")
@Embeddable
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String number;
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;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
- 修改User实体,嵌入一个Book实体的列表
@ElementCollection(targetClass = Book.class)
List<Book> books;
自动创建的表结构是两个实体分别创建一个表,使用了一个中间表来存储关联关系
JPA实体间的映射
- 一对一映射 @OneToOne,这个是通过外键来关联的。注解在那个实体里面就会在这个实体里面生成一个关联的外键
// in user entity
@OneToOne
UserEx userEx; // 这个实体的id会在user表里面有一个外键字段
- 一对多映射@OneToMany,这个需要添加targetEntity属性。这个是通过中间表来关联
- 多对一映射@ManyToOne,这个会再多的一方创建一个外键
// in user entity
@ManyToOne(cascade = CascadeType.ALL)
Tag tag;
- 多对多映射@ManyToMany
JPA级联操作
- 级联操作都是 javax.persistence.CascadeType 里面的枚举,级联可以用在上面的映射关系注解
- PERSIST 如果父实体持久存在,则其所有相关实体也将被持久化
- MERGE 如果父实体被合并,则其所有相关实体也将被合并
- DETACH 如果父实体被分离,那么它的所有相关实体也将被分离
- REFRESH 如果父实体被刷新,则其所有相关实体也将被刷新
- REMOVE 如果父实体被移除,则其所有相关实体也将被移除
- ALL 所有上述级联操作都可以应用于与父实体相关的实体
JPQL JPA的查询语言
- JPQL(Java持久性查询语言)是一种面向对象的查询语言,用于对持久实体执行数据库操作。 JPQL不使用数据库表,而是使用实体对象模型来操作SQL查询
Spring Data 介绍
Spring Data 提供了一个数据访问层的抽象,这个抽象定义了基本的访问数据的接口。这里的数据来源就不一定是数据库了,也可能是缓存,也可能是nosql数据库等,针对不同的底层数据存储方式都可以使用同一套代码进行访问
Spring Data 核心概念
- spring data将数据访问对象抽象成Repository,这个是一个标记接口,仅仅是标记这个是个数据访问对象。这个接口是一个泛型接口,T代表的是存储的实体(比如这个类是操作用户的类,那么这个T就是User),ID代表这个实体对应的唯一标识
@Indexed
public interface Repository<T, ID> {
}
- Repository仅仅是个标记接口,并没有定义任何方法。spring data提供了一个包含基本操作的Repository,CrudRepository(这个接口是Repository的子接口)
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
- 对于不同的存储技术spring data提供了不同的接口,比如JpaRepository,MongoRepository
spring data中的这些接口都不需要实现,具体的实现是由spring data通过动态代理针对不同的存储技术实现的,我们只需要按照约定将接口定义好就可以了
spring data demo
- 这里先用 spring boot 来完成一个 spring data 的demo
- 依赖(这里用了 jpa 如果不清楚就先当成一个orm框架就行了)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
- 创建测试数据库(demo数据库 和 user测试表)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`number` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- 定义实体类
@Entity
@Table(name = "user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String number;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
- 定义User的Repository接口
public interface UserRepository extends CrudRepository<User,Long> { }
- 虽然我们定义了Repository接口但是并没有实现我们需要使用注解@EnableJpaRepositories激活Repository的自动代理
@SpringBootApplication
@RestController
@EnableJpaRepositories
public class App {
public static void main(String[] args){
SpringApplication.run(App.class,args);
}
}
- 访问数据库(记得配置数据库链接信息哦,DataSourcePerporties)
@SpringBootApplication
@RestController
@EnableJpaRepositories
public class App {
@Autowired
UserRepository userRepository;
@RequestMapping("/insertUser")
public User insertUser(){
User user = new User();
user.setName("dapi");
user.setNumber("123456");
user = userRepository.save(user);
return user;
}
public static void main(String[] args){
SpringApplication.run(App.class,args);
}
}
@EnableJpaRepositories这个注解是激活 jpa 的方式来生成代理,有些时候我们的应用可能还用了其他的存储技术,比如mogodb这个时候就可以使用@EnableMogoRepositories,@EnableJpaRepositories可以添加一个basePackages属性可以指定只代理特定包下面的Repositor接口
↓ 详细介绍使用spring data的详细步骤
定义Repository接口
定义我们特定的repository接口我们有好几种选择:Repository ,CrudRepository,PagingAndSortingRepository(支持排序和分页的方法定义,这个没有什么特别的我们也可以按照规则自己定义方法),当然也可以不继承任何的接口(前面已经所有这些接口仅仅是一个标记接口)
有些时候我们会定义一个根据项目需求写的一个父Repository接口,其他所有的Repository接口都需要继承这个接口。这个父Repository接口是不需要被代理的,这个时候可以在父Repository接口使用注解 @NoRepositoryBean
如果不想继承spring data的Repository标记接口,可以使用注解
@RepositoryDefinition(domainClass = User.class,idClass = Long.class)
public interface UserRepository {
<S extends User> S save(S user);
}
- 多种存储技术混合用的情况,比如JPA 和 MongoDB同时用的时候。有个尴尬的事情就是,同一个Repository究竟是操作 sql数据库呢还是操作 MongoDB呢?原则就是只要有能够区分这两种操作的代码就行,比如这个Repository继承的是JpaRepository那这个就是操作sql数据库,如果操作的实体使用了@Entity注解那就是操作sql了,如果实体使用@Docment那就是操作mongodb了,如果Repository没有使用特定的Repository,实体同时使用了@Entity和@Docment那这个时候就只能使用包名来区分了,比如jpa的Repository都在一个包里面@EnableJpaRepositories(basePackages = "com.suse.re.jpa")
定义查询方法
- 在Repository接口中按照特定的方式定义方法,就可以实现特定的查询(当然不止这一种方式,后面在介绍其他方式)
- 查询方法find…By,read…By, query…By,count…By, get…By。只要按照这种方式定义方法名,就可以实现查询(还有很多其他的支持,后面结合 jpa 一起说)
@RepositoryDefinition(domainClass = User.class,idClass = Long.class)
public interface UserRepository {
<S extends User> S save(S user);
List<User> findByName(String name);
}
- 分页和排序处理,只需要在方法后面添加参数就可以处理了
@RepositoryDefinition(domainClass = User.class,idClass = Long.class)
public interface UserRepository {
<S extends User> S save(S user);
// 定义排序
List<User> findByName(String name, Sort orders);
}
@RequestMapping("/filterName")
public List<User> filterName(){
// 使用排序
Sort orders = new Sort(Sort.Direction.DESC,"id");
return userRepository.findByName("dapi",orders);
}
@RepositoryDefinition(domainClass = User.class,idClass = Long.class)
public interface UserRepository {
<S extends User> S save(S user);
List<User> findByName(String name, Pageable pageable);
}
Pageable pageable = PageRequest.of(0,1);
return userRepository.findByName("dapi",pageable);
这两个参数不能同时使用,如果想要同时支持分页和选择,仅仅是构造Pageable的时候不同。PageRequest pageable = PageRequest.of(0,1,Sort.Direction.DESC,"id");
创建Repository实例
- 注解@EnableJpaRepositories
- 使用EntityManager
class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
@Transactional
public <S extends T> S save(S entity) {
}
}
spring data web
- 开启 spring data web 支持,@EnableSpringDataWebSupport
- 直接将参数转化成 Pageable 和 Sort
// http://localhost:8080/filterName?page=0&size=1&sort=id,DESC
@RequestMapping("/filterName")
public List<User> filterName(Pageable pageable){
// PageRequest pageable = PageRequest.of(0,1,Sort.Direction.DESC,"id");
return userRepository.findByName("dapi",pageable);
}
Spring Data JPA简介
- spring data jpa 是构建在spring data 之上的一个简化 jpa 操作的库
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置
- 两个注解:@EnableJpaRepositories 和 @EnableTransactionManagement
@SpringBootApplication
@RestController
@EnableJpaRepositories
@EnableTransactionManagement
public class App {
@Autowired
UserRepository userRepository;
@RequestMapping("/insertUser")
public User insertUser(){
User user = new User();
user.setName("dapixxx");
user.setNumber("123456");
user = userRepository.save(user);
return user;
}
public static void main(String[] args){
SpringApplication.run(App.class,args);
}
}
保存实体
- 保存实体最后会调用 EntityManager 的 persist 方法或者是 merge方法 判断是更具Id来判断了,如果Id为空会调用 persist 如果id不为空则调用 merge
查询方法
首先要知道有三种方式可以写查询方法:方法名,JPA NamedQueries ,@Query
方法名:按照特定的方式命名函数,spring data 最后会解析这个函数名
NamedQueries: 这个是JPA里面注解查询
Query: 这个是spring data 的一个扩展,和上面那个类似
按照方法名:比如 List<User> findByEmailAddressAndLastname(String emailAddress, String lastname); 这个方法名最后会被翻译成 select u from User u where u.emailAddress = ?1 and u.lastname = ?2。 具体支持那些关键字就看这里吧。
@NamedQueries(JPQL) & @NamedNativeQuery(支持sql)
@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
- @Query
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
- @Query & Like
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
- @Query & sql
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery =
true)
User findByEmailAddress(String emailAddress);
}
- @Query & sql & page
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
- @Query & name parameters
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname =:lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
}
投影
- 简单的说就是实体的有些部分不想返回,我们就可以用投影来操作
- 建立一个接口,将需要返回的字段声明成方法
class Person {
@Id UUID id;
String firstname, lastname;
Address address;
static class Address {
String zipCode, city, street;
}
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
End