Spring Data JPA 学习笔记

说明

首先来说JPA是一个持久化规范,也就是说当我们用JPA的时候我们不需要去选面向Hibernate的api编程了,这样就大大降低了偶和度了
这里介绍spring配置版和springboot配置版(推荐)


spring配置版

引入

JPA是一种规范,那么它的编程有哪些要求呢?
引入下载的jar包导入lib文件夹,然后我们的在src下面加上一个META-INF目录在该文件夹下面加上一个persistence.xml文件,这个文件的规范写法

persistence.xml

<?xml version="1.0"?>
<persistence 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_1_0.xsd" version="1.0">
<persistence-unit name="jun" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
            <property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver"/>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.connection.password" value="root"/>
            <property name="hibernate.connection.url" value="jdbc:mysql:///jpa??autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false"/>
            <property name="hibernate.max_fetch_depth" value="3"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.jdbc.fetch_size" value="18"/>
            <property name="hibernate.jdbc.batch_size" value="10"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="false"/> 
        </properties>
    </persistence-unit>
</persistence>

这个文件中的写话我们可以再hibernate的jpa实现包里面找到对应的例子,这里我是使用的是hibernate来实现JPA实现,上面的配置也都和heibernate差不太多值得注意的:

<persistence-unit name="jun" transaction-type="RESOURCE_LOCAL">
  1. Persistence-unit持久性化单元,我们可以随便命名但是在后面回到他的,通过它来找到相关的配置信息
  2. transaction-type 是指事物的类型,在通常情况下我们是本地的,但是当我们遇到两个数据库保存的时候 我们会到到JTA事物来控制事务,也就是二次提交

实体类来映射数据表

Persion.java

@Entity //就是告诉JPA我是一个实体bean
@Table(name="t_person")//作为修改表名,默认情况是类名
public class Person {
    /**
    * GenerationType.AUTO它会根据数据库方言来选择正确的生成策略
    * 默认情况下就是AUTO
    */
    @Id//映射主键
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Integer id;
    // 映射一列到数据库中 length是指字段的长度
    // nullable是指是否为空,默认情况是空
    // 当我们不想让类字段名与数据库名一样的时候用到的
    @Column(length=10,nullable=false,name="personname")
    private String name;
    // 映射日期类型TemporalType有好几种映射方式
    // 我们可以根据自己的需求选择正确的映射方式
    @Temporal(TemporalType.DATE)
     @Column(nullable=false)
    private Date birthday;
    // 用枚举类型来映射性别EnumType有好几种映射方式
    // 这里使用的是枚举的String类型,我们也一个选择枚举的索引
    @Enumerated(EnumType.STRING)
    @Column(nullable=false,length=5)
    private Sex sex = Sex.MEN;
     // 对应大文本和图片就是二进制
    @Lob 
    private String info;
    // 支持延迟加载(减少内存的消耗)
    @Lob @Basic(fetch=FetchType.LAZY)
    private Byte[] image;
    // 不想这个字段与数据库表映射
    @Transient
    private String imagepath;
    // 版本标识 防止脏读
    @Version
    private Integer version;
    // get set //
}

利用jpa来操作数据库

Test.java

@Test
public void testSave() {
    /**
    * jun是在persistence.xml中持久化单元名称
    */
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    //--->>SessionFactory-->>session-->>begin
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();//开启事物
    Person person = new Person("刘文军");
    person.setBirthday(new Date());
    em.persist(person);
    System.out.println("----->>"+person.getSex().getName());
    em.getTransaction().commit();
    em.close();
    factory.close();
}
  1. 这里我们EntityManagerFactory就相当于Hibernate中的SessionFactory,
    然而EntityManager就相当于Hibernate中的Session。
  2. 在JPA中我们获得它的方式是Persistence.createEntityManagerFactory("jun");
    jun是在persistence.xml中持久化单元名称
  3. EntityManager em = factory.createEntityManager();
    其中EntityManager中的几种操作数据库方法有
    Persist(),find(),getReference(),merge(),remove()这里我就列出几种常见的
  4. 这里说有的getReference()就相当于Hibernate中的Load() 支持Lazy加载,
    getReference它会查找一个代理对象,当访问它的属性的时候它才会向数据库操作
  5. 但是值得注意的这个必须确保Session是打开状态

实体bean的几种状态

一、新建状态

new一个实体的时候它所处于的状态

二、托管状态

这个状态非常重要,当一个实体bean是托管状态的时候我们修改它的属性值得话,即使我们没有用到hibernate中的相关方法操作数据库,她也会同步到数据库里的,托管就是从数据库里面查询到的一个对象,记住,托管状态必须的和事物关联上来

    em.getTransaction().begin();
    person.setName("小刘");
    em.getTransaction().commit();
    em.close();
    factory.close();

三、游离状态

当我们使用EntityManager中的clear()方法的时候,它会把事物管理器中的所有实体变成游离状态,
处在游离状态的实体bean,我们修改它的属性它不会同步到数据库中的

    em.getTransaction().begin();
    em.clear(); // 把实体管理器中的所有实体都变成游离状态
    person.setName("老刘");
    em.merge(person);
    em.getTransaction().commit();
    em.close();
    factory.close();

四、关闭状态

就是当我们把Session关闭的时候,实体bean处于的状态

JPA中使用EJQL语句

查询

public void query() {
    /**
    * jun是在persistence.xml中持久化单元名称
    */
    EntityManagerFactory factory=Persistence.createEntityManagerFactory("jun");
    //--->>SessionFactory-->>session-->>begin
    EntityManager em = factory.createEntityManager();
    Query query = em.createQuery("select o from Person o where o.id=?1");
    query.setParameter(1, 2);
    //
    /**
    * Person person = (Person)query.getSingleResult();
    * 这个就相当于Hibernate中的uniqueResult();
    * 如果没有查询到数据的话会出错的,所有我们一般不这样做
    * 我们先得到List 然后遍历它
    */
    List<Person> list = query.getResultList();
    for(Person person : list) 
    System.out.println("---------->>" + person.getName());
    query = em.createQuery("select count(o) from Person o ");
    Long count = (Long)query.getSingleResult();
    System.out.println("---------->>" + count);
    em.close(); 
    factory.close();
}

删除查询(记得要开启事物)

public void deletequery() {
    /**
    * jun是在persistence.xml中持久化单元名称
    */
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Query query = em.createQuery("delete from Person o where o.id=?1");
    query.setParameter(1, 2);
    query.executeUpdate();
    em.getTransaction().commit();
    em.close(); 
    factory.close();
}

修改查询(记得要开启事物)

public void updatequery() {
    /**
    * jun是在persistence.xml中持久化单元名称
    */
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Query query = em.createQuery("update from Person o set o.name=:name where o.id=:id");
    query.setParameter("name", "小刘");
    query.setParameter("id", 3);
    query.executeUpdate();
    em.getTransaction().commit();
    em.close(); 
    factory.close();
}

刷新方法

public void testgetPerson() {
    /**
    * jun是在persistence.xml中持久化单元名称
    */
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    //--->>SessionFactory-->>session-->>begin
    EntityManager em = factory.createEntityManager();
    Person person = em.find(Person.class, 1);
    /**
    * 1分钟内
    * 比方说这里有人直接操作数据库改了数据,我们如何来得到person呢
    * 有人说我们可以再执行一下find方法,这个事不可以的,因为你再次
    * 执行find方法的时候EntityManager会中一级缓存中取得id是1的
    * Person数据,也就是说不能得到最新的数据,那么如果我们想得到最
    * 新的数据怎么办呢?JPA帮我们做了一个刷新的方法em.refresh(person);
    * 通过它我们就可以得到最新的数据了
    */
    em.refresh(person);
    System.out.println("----->>"+person.getName());
    em.close();
    factory.close();
}

JPA中实现表间关联关系

一的多

@Entity
@Table(name="t_order")
public class Order {

    @Id
    @Column(length=200)
    private String orderid;
    @Column(nullable=false)
    private Float amount = 0f;
    /**
     * cascade=CascadeType.REFRESH设置级联刷新
     * CascadeType.PERSIST设置级联保存
     * CascadeType.MERGE设置级联更新
     * CascadeType.REMOVE设置级联删除
     * CascadeType.ALL设置四种级联
     * 这四种级联都是对应的四种方法才能起作用PERSIST(),MERGE(),REMOVE(),REFRESH()
     * fetch=FetchType.LAZY支持LAzy加载,只有用到该属性的时候才会读集合数据
     * 注意这时我们必须保持EntityManager是开着的,默认是延迟加载
     * mappedBy="order"指定OrderItem类里面的order这个属性来维护关系
     */
    @OneToMany(cascade={CascadeType.ALL},
                    fetch=FetchType.LAZY,
                    mappedBy="order")
    private Set<OrderItem> items = new HashSet<OrderItem>();

    // get set //
}

为了我们添加Set集合方便我们通常的情况下我们的写一个添加Set的方法

public void addOrderItem(OrderItem orderItem) {
    orderItem.setOrder(this);//this就是Order
    items.add(orderItem);
}

多的一

@Entity
@Table(name="t_orderitem")
public class OrderItem {

    @Id
    @GeneratedValue
    private Integer id;
    @Column(length=40,nullable=false)
    private String productName;
    @Column(nullable=false)
    private Float sellPrice = 0f;
    /**
     * optional=false指定该字段不能空
     * @JoinColumn(name="order_id")指明关联关系的子段名
     * 即定义外键的名称
     *   
     * */
    @ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false)
    @JoinColumn(name="order_id")
    private Order order;
    // get set //
}

测试

public void testCreate() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Order order = new Order();
    order.setAmount(598f);
    order.setOrderid(UUID.randomUUID().toString());
    OrderItem orderItem1 = new OrderItem();
    orderItem1.setProductName("篮球");
    orderItem1.setSellPrice(256f);
    order.addOrderItem(orderItem1);
    OrderItem orderItem2 = new OrderItem();
    orderItem2.setProductName("女人");
    orderItem2.setSellPrice(800000f);
    order.addOrderItem(orderItem2);
    em.persist(order);
    em.getTransaction().commit();
    em.close();
    factory.close();
}

这里我们并没有保存orderIterm
但是我们在数据库中依然能看到orderIterm的数据
保存到数据库里面了,这里就是设置级联保存的缘故
CascadeType.PERSIST设置级联保存

一对一

Person.java

@Entity
@Table(name="t_person")
public class Person {

    @Id
    @GeneratedValue
    private Integer id;
    @Column(length=10,nullable=false)
    private String name;
    @OneToOne(optional=false,cascade={CascadeType.ALL})
    @JoinColumn(name="idcard_id")
    private IDCard idCard;
    public Person() {}
    public Person(String name) {
        this.name = name;
    }
}

IDCard.java

@Entity
@Table(name="t_idcard")
public class IDCard {

    @Id
    @GeneratedValue
    private Integer id;
    @Column(length=18,nullable=false)
    private String cardno ;
    @OneToOne(mappedBy="idCard",
            cascade={CascadeType.REFRESH,CascadeType.PERSIST,CascadeType.MERGE})
    private Person person;
    public IDCard() {}
    public IDCard(String cardno) {
    this.cardno = cardno;
    }
}

测试

public void testCreateTable() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Person person = new Person("小刘");
    IDCard idCard = new IDCard("411521198409131915");
    idCard.setPerson(person);
    person.setIdCard(idCard);
    em.persist(person);
    em.getTransaction().commit();
    em.close();
    factory.close();
}

多对多

Student.java

@Entity
@Table(name="t_student")
public class Student {

    @Id
    @GeneratedValue
    private Integer id;
    @Column(length=10,nullable=false)
    private String name;
    /**
     * @JoinTable(name="t_stu_tea",
            joinColumns=@JoinColumn(name="student_id"),
            inverseJoinColumns=@JoinColumn(name="teacher_id"))
        创建中间表来维护两个表的关系
     * joinColumns=@JoinColumn(name="student_id")定义维护端在中间表中的关联字段
     * inverseJoinColumns=@JoinColumn(name="teacher_id")定义被维护端在中间表中的关联字段
     * 
     */
    @ManyToMany(cascade=CascadeType.REFRESH)
    @JoinTable(name="t_stu_tea",
            joinColumns=@JoinColumn(name="student_id"),
            inverseJoinColumns=@JoinColumn(name="teacher_id"))
    private Set<Teacher> teachers = new HashSet<Teacher>();
    public Student() {}
    public Student(String name) {
        this.name = name;
    }
}
  • 我们通常把set集合方法重写
public void addTeacher(Teacher teacher) {
    this.teachers.add(teacher);
}
public void removeTeacher(Teacher teacher) {
    if(this.teachers.contains(teacher)) {
    this.teachers.remove(teacher);
    }
}

Teacher.java

@Entity
@Table(name="t_teacher")
public class Teacher {

    @Id
    @GeneratedValue
    private Integer id;

    @Column(length=12,nullable=false)
    private String name;

    @ManyToMany(cascade=CascadeType.REFRESH,mappedBy="teachers")
    private Set<Student> students = new HashSet<Student>();

    public Teacher() {}

    public Teacher(String name) {
        this.name = name;
    }
}

测试

@Test
public void testCreateTable() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Student student = new Student("小刘");
    Teacher teacher = new Teacher("肖老师");
    em.persist(student);
    em.persist(teacher);
    em.getTransaction().commit();
    em.close();
    factory.close();
}

/**
* 建立学生跟老师的关系
*/
@Test
public void buildTS() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Student student = em.find(Student.class, 1);
    student.addTeacher(em.getReference(Teacher.class, 1));
    em.getTransaction().commit();
    em.close();
    factory.close();
}

/**
* 解除学生跟老师的关系
*/
@Test
public void deleteTS() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Student student = em.find(Student.class, 1);
    student.removeTeacher(em.getReference(Teacher.class, 1));
    em.getTransaction().commit();
    em.close();
    factory.close();
}

/**
* 删除老师
*/
@Test
public void deleteTeacher() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    /**
    * 删除老师的时候一定的解除掉老师和学生的关系
    */
    Student student = em.find(Student.class, 1);
    student.removeTeacher(em.getReference(Teacher.class, 1));
    em.remove(em.getReference(Teacher.class, 1));
    em.getTransaction().commit();
    em.close();
    factory.close();
}

/** 
*删除学生
*/
@Test
public void deleteStudent() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();

    /**
    * 这个为什么不需要解除关系呢,主要是因为学生这一段是维护端
    */
    Student student = em.find(Student.class, 1);
    em.remove(student);
    em.getTransaction().commit();
    em.close();
    factory.close();
}

联合主键

我们单独写一个类作为主键,但是这个类的要满足3条规则

  1. 它必须实现Serializable接口
  2. 它必须重写字段的hashCode()和equals()方法
  3. 要在类上表明@Embeddable,这个注解的意思就是让它的属性在AirLine也正常做属性
    那么我们在主体类里面用这个主键类做为id,在id上面加上@EmbeddedId表明就是用主键类的属性作为主键,并映射到数据库表中

AirLine.java

@Entity
@Table(name="t_airline")
public class AirLine {
    @EmbeddedId
    private AirLinePK id;
    @Column(length=10,nullable=false)
    private String name;
}

AirLinePK.java

@Embeddable
public class AirLinePK implements Serializable{
    // 序列UID
    private static final long serialVersionUID = -7125628704970675246L;

    @Column(length=20)
    private String startLine;
    @Column(length=20)
    private String endLine;
    // 注意这里一定的时间他HashCode()和equals()方法
}

测试

public void testPK() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("jun");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    AirLine airLine = new AirLine("PEK","SHG","北京飞往上海");
    em.persist(airLine);
    em.getTransaction().commit();
    em.close();
    factory.close();
}

使用步骤和上边spring配置版介绍基本一致,关键是要理解一对一、一对多、多对一、多对多的关联关系


推荐使用Spring Boot

配置Maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

配置JPA

# maven打包 clean compile package
spring:
    # 热重启
    devtools:
    restart:
      enabled: true
    # 数据源
    datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql:///qnzf?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
      username: root
      password: 

    # JPA配置
    jpa:
      show-sql: true
      hibernate:
        naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        ddl-auto: update

    # tomcat
    server:
      port: 8080
      tomcat:
        Uri-encoding: UTF-8

通过实体类映射数据表

// LED.java
@Entity
@Table(name = "tbl_led")
public class LED {
    public LED() {
        super();
    }
    // 指定id
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 唯一键
    @Column(unique = true)
    private Integer pin; // LED连接的针脚位
}

通过接口类继承使用JPA内封装实现的方法

// LEDRepository.java
public interface LEDRepository extends JpaRepository<LED, Long> { }

最后可以在Controller类中使用

@Autowired
private LEDPowerRepository ledPowerRepository;

个别使用方法可以浏览其他教程介绍加强一下

Spring For All 社区 Spring Data JPA 从入门到进阶系列教程
SpringBoot实战SpringDataJPA
使用 Spring Data JPA 简化 JPA 开发

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

推荐阅读更多精彩内容