:) 本篇會(huì)分別介紹 Spring Data 祭埂,JPA 胚宦,Spring Data JPA
JPA簡(jiǎn)介
jpa全稱(chēng)是 Java Persistence API,jpa定義了各種注解(用來(lái)定義實(shí)體,映射關(guān)系)掏愁。JPA僅僅是一個(gè)規(guī)范歇由,它的實(shí)現(xiàn)比較出名的是Hibernate
JPA實(shí)體
- 實(shí)體可以簡(jiǎn)單理解成一組狀態(tài)的集合
- 實(shí)體需要能夠持久化并有持久化標(biāo)識(shí),支持事務(wù)
- JPA中的實(shí)體使用注解@Entity標(biāo)記果港,實(shí)體的id使用@Id標(biāo)記沦泌,實(shí)體對(duì)應(yīng)的數(shù)據(jù)庫(kù)表名使用@Table標(biāo)記,實(shí)體對(duì)應(yīng)數(shù)據(jù)庫(kù)字段使用@Column標(biāo)記辛掠,主鍵生成策略使用@GeneratedValue標(biāo)記
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "user")
public class User implements Serializable {
@Id
// 主鍵自動(dòng)增長(zhǎng)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
// 默認(rèn)對(duì)應(yīng)字段 number
private String number;
// 省略 getter & setter
}
實(shí)體還需要一個(gè)默認(rèn)的無(wú)參構(gòu)造函數(shù)
使用spring boot簡(jiǎn)單測(cè)試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下面創(chuàng)建一個(gè)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產(chǎn)品 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 添加對(duì)應(yīng)的持久化類(lèi) -->
<class>com.suse.yudapi.entity.User</class>
<properties>
<!-- jpa中連接數(shù)據(jù)庫(kù) -->
<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>
- 創(chuàng)建一個(gè)測(cè)試程序
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的方法來(lái)完成的谢谦,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中的集合映射
- 類(lèi)似一對(duì)多的關(guān)系回挽,但是沒(méi)有雙向關(guān)聯(lián)關(guān)系,也不能級(jí)聯(lián)操作猩谊。
- 新建一個(gè)Book實(shí)體千劈,一個(gè)用戶持有多個(gè)book實(shí)體,注意這個(gè)實(shí)體有一個(gè)Embeddable注解牌捷。這個(gè)注解表示這個(gè)實(shí)體可以嵌入其他實(shí)體
@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實(shí)體墙牌,嵌入一個(gè)Book實(shí)體的列表
@ElementCollection(targetClass = Book.class)
List<Book> books;
自動(dòng)創(chuàng)建的表結(jié)構(gòu)是兩個(gè)實(shí)體分別創(chuàng)建一個(gè)表,使用了一個(gè)中間表來(lái)存儲(chǔ)關(guān)聯(lián)關(guān)系
JPA實(shí)體間的映射
- 一對(duì)一映射 @OneToOne宜鸯,這個(gè)是通過(guò)外鍵來(lái)關(guān)聯(lián)的憔古。注解在那個(gè)實(shí)體里面就會(huì)在這個(gè)實(shí)體里面生成一個(gè)關(guān)聯(lián)的外鍵
// in user entity
@OneToOne
UserEx userEx; // 這個(gè)實(shí)體的id會(huì)在user表里面有一個(gè)外鍵字段
- 一對(duì)多映射@OneToMany,這個(gè)需要添加targetEntity屬性淋袖。這個(gè)是通過(guò)中間表來(lái)關(guān)聯(lián)
- 多對(duì)一映射@ManyToOne鸿市,這個(gè)會(huì)再多的一方創(chuàng)建一個(gè)外鍵
// in user entity
@ManyToOne(cascade = CascadeType.ALL)
Tag tag;
- 多對(duì)多映射@ManyToMany
JPA級(jí)聯(lián)操作
- 級(jí)聯(lián)操作都是 javax.persistence.CascadeType 里面的枚舉,級(jí)聯(lián)可以用在上面的映射關(guān)系注解
- PERSIST 如果父實(shí)體持久存在即碗,則其所有相關(guān)實(shí)體也將被持久化
- MERGE 如果父實(shí)體被合并焰情,則其所有相關(guān)實(shí)體也將被合并
- DETACH 如果父實(shí)體被分離,那么它的所有相關(guān)實(shí)體也將被分離
- REFRESH 如果父實(shí)體被刷新剥懒,則其所有相關(guān)實(shí)體也將被刷新
- REMOVE 如果父實(shí)體被移除内舟,則其所有相關(guān)實(shí)體也將被移除
- ALL 所有上述級(jí)聯(lián)操作都可以應(yīng)用于與父實(shí)體相關(guān)的實(shí)體
JPQL JPA的查詢語(yǔ)言
- JPQL(Java持久性查詢語(yǔ)言)是一種面向?qū)ο蟮牟樵冋Z(yǔ)言,用于對(duì)持久實(shí)體執(zhí)行數(shù)據(jù)庫(kù)操作初橘。 JPQL不使用數(shù)據(jù)庫(kù)表验游,而是使用實(shí)體對(duì)象模型來(lái)操作SQL查詢
Spring Data 介紹
Spring Data 提供了一個(gè)數(shù)據(jù)訪問(wèn)層的抽象,這個(gè)抽象定義了基本的訪問(wèn)數(shù)據(jù)的接口保檐。這里的數(shù)據(jù)來(lái)源就不一定是數(shù)據(jù)庫(kù)了耕蝉,也可能是緩存,也可能是nosql數(shù)據(jù)庫(kù)等夜只,針對(duì)不同的底層數(shù)據(jù)存儲(chǔ)方式都可以使用同一套代碼進(jìn)行訪問(wèn)
Spring Data 核心概念
- spring data將數(shù)據(jù)訪問(wèn)對(duì)象抽象成Repository垒在,這個(gè)是一個(gè)標(biāo)記接口,僅僅是標(biāo)記這個(gè)是個(gè)數(shù)據(jù)訪問(wèn)對(duì)象扔亥。這個(gè)接口是一個(gè)泛型接口场躯,T代表的是存儲(chǔ)的實(shí)體(比如這個(gè)類(lèi)是操作用戶的類(lèi)谈为,那么這個(gè)T就是User),ID代表這個(gè)實(shí)體對(duì)應(yīng)的唯一標(biāo)識(shí)
@Indexed
public interface Repository<T, ID> {
}
- Repository僅僅是個(gè)標(biāo)記接口踢关,并沒(méi)有定義任何方法伞鲫。spring data提供了一個(gè)包含基本操作的Repository,CrudRepository(這個(gè)接口是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();
}
- 對(duì)于不同的存儲(chǔ)技術(shù)spring data提供了不同的接口签舞,比如JpaRepository榔昔,MongoRepository
spring data中的這些接口都不需要實(shí)現(xiàn),具體的實(shí)現(xiàn)是由spring data通過(guò)動(dòng)態(tài)代理針對(duì)不同的存儲(chǔ)技術(shù)實(shí)現(xiàn)的瘪菌,我們只需要按照約定將接口定義好就可以了
spring data demo
- 這里先用 spring boot 來(lái)完成一個(gè) spring data 的demo
- 依賴(這里用了 jpa 如果不清楚就先當(dāng)成一個(gè)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>
- 創(chuàng)建測(cè)試數(shù)據(jù)庫(kù)(demo數(shù)據(jù)庫(kù) 和 user測(cè)試表)
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
- 定義實(shí)體類(lèi)
@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接口但是并沒(méi)有實(shí)現(xiàn)我們需要使用注解@EnableJpaRepositories激活Repository的自動(dòng)代理
@SpringBootApplication
@RestController
@EnableJpaRepositories
public class App {
public static void main(String[] args){
SpringApplication.run(App.class,args);
}
}
- 訪問(wèn)數(shù)據(jù)庫(kù)(記得配置數(shù)據(jù)庫(kù)鏈接信息哦,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這個(gè)注解是激活 jpa 的方式來(lái)生成代理嘹朗,有些時(shí)候我們的應(yīng)用可能還用了其他的存儲(chǔ)技術(shù)师妙,比如mogodb這個(gè)時(shí)候就可以使用@EnableMogoRepositories,@EnableJpaRepositories可以添加一個(gè)basePackages屬性可以指定只代理特定包下面的Repositor接口
↓ 詳細(xì)介紹使用spring data的詳細(xì)步驟
定義Repository接口
定義我們特定的repository接口我們有好幾種選擇:Repository 屹培,CrudRepository默穴,PagingAndSortingRepository(支持排序和分頁(yè)的方法定義,這個(gè)沒(méi)有什么特別的我們也可以按照規(guī)則自己定義方法)褪秀,當(dāng)然也可以不繼承任何的接口(前面已經(jīng)所有這些接口僅僅是一個(gè)標(biāo)記接口)
有些時(shí)候我們會(huì)定義一個(gè)根據(jù)項(xiàng)目需求寫(xiě)的一個(gè)父Repository接口蓄诽,其他所有的Repository接口都需要繼承這個(gè)接口。這個(gè)父Repository接口是不需要被代理的媒吗,這個(gè)時(shí)候可以在父Repository接口使用注解 @NoRepositoryBean
如果不想繼承spring data的Repository標(biāo)記接口仑氛,可以使用注解
@RepositoryDefinition(domainClass = User.class,idClass = Long.class)
public interface UserRepository {
<S extends User> S save(S user);
}
- 多種存儲(chǔ)技術(shù)混合用的情況,比如JPA 和 MongoDB同時(shí)用的時(shí)候闸英。有個(gè)尷尬的事情就是锯岖,同一個(gè)Repository究竟是操作 sql數(shù)據(jù)庫(kù)呢還是操作 MongoDB呢?原則就是只要有能夠區(qū)分這兩種操作的代碼就行甫何,比如這個(gè)Repository繼承的是JpaRepository那這個(gè)就是操作sql數(shù)據(jù)庫(kù)出吹,如果操作的實(shí)體使用了@Entity注解那就是操作sql了,如果實(shí)體使用@Docment那就是操作mongodb了辙喂,如果Repository沒(méi)有使用特定的Repository捶牢,實(shí)體同時(shí)使用了@Entity和@Docment那這個(gè)時(shí)候就只能使用包名來(lái)區(qū)分了,比如jpa的Repository都在一個(gè)包里面@EnableJpaRepositories(basePackages = "com.suse.re.jpa")
定義查詢方法
- 在Repository接口中按照特定的方式定義方法巍耗,就可以實(shí)現(xiàn)特定的查詢(當(dāng)然不止這一種方式扣甲,后面在介紹其他方式)
- 查詢方法find…By,read…By杖刷, query…By裹虫,count…By, get…By娄琉。只要按照這種方式定義方法名次乓,就可以實(shí)現(xiàn)查詢(還有很多其他的支持吓歇,后面結(jié)合 jpa 一起說(shuō))
@RepositoryDefinition(domainClass = User.class,idClass = Long.class)
public interface UserRepository {
<S extends User> S save(S user);
List<User> findByName(String name);
}
- 分頁(yè)和排序處理,只需要在方法后面添加參數(shù)就可以處理了
@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);
這兩個(gè)參數(shù)不能同時(shí)使用票腰,如果想要同時(shí)支持分頁(yè)和選擇城看,僅僅是構(gòu)造Pageable的時(shí)候不同。PageRequest pageable = PageRequest.of(0,1,Sort.Direction.DESC,"id");
創(chuàng)建Repository實(shí)例
- 注解@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
- 開(kāi)啟 spring data web 支持杏慰,@EnableSpringDataWebSupport
- 直接將參數(shù)轉(zhuǎn)化成 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簡(jiǎn)介
- spring data jpa 是構(gòu)建在spring data 之上的一個(gè)簡(jiǎn)化 jpa 操作的庫(kù)
依賴
<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>
配置
- 兩個(gè)注解:@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);
}
}
保存實(shí)體
- 保存實(shí)體最后會(huì)調(diào)用 EntityManager 的 persist 方法或者是 merge方法 判斷是更具Id來(lái)判斷了测柠,如果Id為空會(huì)調(diào)用 persist 如果id不為空則調(diào)用 merge
查詢方法
首先要知道有三種方式可以寫(xiě)查詢方法:方法名,JPA NamedQueries 缘滥,@Query
方法名:按照特定的方式命名函數(shù)轰胁,spring data 最后會(huì)解析這個(gè)函數(shù)名
NamedQueries: 這個(gè)是JPA里面注解查詢
Query: 這個(gè)是spring data 的一個(gè)擴(kuò)展,和上面那個(gè)類(lèi)似
按照方法名:比如 List<User> findByEmailAddressAndLastname(String emailAddress, String lastname); 這個(gè)方法名最后會(huì)被翻譯成 select u from User u where u.emailAddress = ?1 and u.lastname = ?2朝扼。 具體支持那些關(guān)鍵字就看這里吧赃阀。
@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);
}
投影
- 簡(jiǎn)單的說(shuō)就是實(shí)體的有些部分不想返回,我們就可以用投影來(lái)操作
- 建立一個(gè)接口擎颖,將需要返回的字段聲明成方法
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