Spring Boot & Spring Data & JPA

:) 本篇会分别介绍 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接口我们有好几种选择:RepositoryCrudRepositoryPagingAndSortingRepository(支持排序和分页的方法定义,这个没有什么特别的我们也可以按照规则自己定义方法),当然也可以不继承任何的接口(前面已经所有这些接口仅仅是一个标记接口)

  • 有些时候我们会定义一个根据项目需求写的一个父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

[2]  spring boot JDBC
[4]  spring boot Mybatis

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容