springboot Jpa解析

1 JPA是什么

JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.
JPA由三个不同的组件构成:

  • 实体(Entities): 在当前版本的JPA中实体是普通Java对象(POJO)。老版本的JPA中实体类需要继承JPA提供的实体基类,但是这样的设计导致框架中存在了严重的依赖关系,测试变得更加困难;所以在新版JPA中不再要求实体类继承任何框架类。
  • 对象-关系型元数据(Object-relational metadata): 应用程序的开发者们必须正确设定Java类和它们的属性与数据库中的表和列的映射关系。有两种设定方式:通过特定的配置文件建立映射;或者使用在新版本中支持的注解.
  • Java持久化查询语句(Java Persistence Query Language - JPQL): 因为JPA旨在建立不依赖于特定的数据库的抽象层,所以它也提供了一种专有查询语言来代替SQL。 这种由JPQL到SQL语言的转换,为JPA提供了支持不同数据库方言的特性,使得开发者们在实现查询逻辑时不需要考虑特定的数据库类型。

2 JPA配置

如下更新pom.xml,将项目中需要的类库添加到依赖(dependencies)设置部分:

<properties>
    <jee.version>7.0</jee.version>
    <h2.version>1.3.176</h2.version>
    <hibernate.version>4.3.8.Final</hibernate.version>
</properties>

<dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>${jee.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>${h2.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
</dependencies>

3 数据库表(Tables)

@Entity
@Table(name = "T_PERSON")
public class Person {
    private Long id;
    private String firstName;
    private String lastName;

    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "FIRST_NAME")
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    @Column(name = "LAST_NAME")
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

附加的注解@Table是可选的,示例中,我们想要所有的表都有前缀T_,所以我们设定了表名T_PERSON。表T_PERSON有3列:ID,FIRST_NAME,LAST_NAME。
这些信息是通过注解@Column和它的属性name提供给JPA中所有具有setter和getter方法的属性创建数据库列,更多的信息:

@Column(name = "FIRST_NAME", length = 100, nullable = false, unique = false)

上例中的代码指明了:限制这个字符串长度为100个字符;该列不能包含空值(null);不必是唯一的。如果试图将空值(null)作为first name插入数据库表的话,就会触发数据库约束冲突,进而导致当前事务回滚。
注解@Id和@GeneratedValue用于告诉JPA该值是主键,而且会自动生成。

运行上面的代码,Hibernate会在我们的本地H2数据库上执行如下操作:

Hibernate: drop table T_PERSON if exists
Hibernate: create table T_PERSON (id bigint generated by default as identity, FIRST_NAME varchar(255), LAST_NAME varchar(255), primary key (id))
Hibernate: insert into T_PERSON (id, FIRST_NAME, LAST_NAME) values (null, ?, ?)

4 继承(Inheritance)

完成项目的设置和上一节中的简单用例之后,我们来看一些更加复杂的用例。假设我们现在想要存储Geek们的个人信息以及他们最喜爱的编程语言信息。由于Geek也是Person,所以我们在Java模式实现中看到它是Person的子类:

@Entity
@Table(name = "T_GEEK")
public class Geek extends Person {
    private String favouriteProgrammingLanguage;
    private List<Project> projects = new ArrayList<Project>();

    @Column(name = "FAV_PROG_LANG")
    public String getFavouriteProgrammingLanguage() {
            return favouriteProgrammingLanguage;
    }

    public void setFavouriteProgrammingLanguage(String favouriteProgrammingLanguage) {
        this.favouriteProgrammingLanguage = favouriteProgrammingLanguage;
    }
}

首先在Geek类设置注解@Entity和@Table;然后使用Hibernate创建新表T_GEEK:

Hibernate: create table T_GEEK (DTYPE varchar(31) not null, id bigint generated by default as identity, FIRST_NAME varchar(255), LAST_NAME varchar(255), FAV_PROG_LANG varchar(255), primary key (id))

我们可以看到Hibernate为这两个实体创建了一个表;新增的名为DTYPE的列用于存储标志位,标识我们存储的是Person还是Geek。我们来添加一些用于持久化Geek到数据库的逻辑(为了更好的可读性,我省略了捕获异常和回滚事务的代码):

private void persistGeek(EntityManager entityManager) {
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();
    Geek geek = new Geek();
    geek.setFirstName("Gavin");
    geek.setLastName("Coffee");
    geek.setFavouriteProgrammingLanguage("Java");
    entityManager.persist(geek);
    geek = new Geek();
    geek.setFirstName("Thomas");
    geek.setLastName("Micro");
    geek.setFavouriteProgrammingLanguage("C#");
    entityManager.persist(geek);
    geek = new Geek();
    geek.setFirstName("Christian");
    geek.setLastName("Cup");
    geek.setFavouriteProgrammingLanguage("Java");
    entityManager.persist(geek);
    transaction.commit();
}

这个方法执行后,表T_PERSON中含有如下记录(也包含我们之前已经插入的Person记录):

sql> select * from t_person;
DTYPE  | ID | FIRST_NAME | LAST_NAME | FAV_PROG_LANG
Person | 1  | Homer      | Simpson   | null
Geek   | 2  | Gavin      | Coffee    | Java
Geek   | 3  | Thomas     | Micro     | C#
Geek   | 4  | Christian  | Cup       | Java

5 实体关系(Relationships)

5.1. 一对一(OneToOne)

@Entity
@Table(name = "T_ID_CARD")
public class IdCard {
    private Long id;
    private String idNumber;
    private Date issueDate;

    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "ID_NUMBER")
    public String getIdNumber() {
        return idNumber;
    }

    public void setIdNumber(String idNumber) {
        this.idNumber = idNumber;
    }

    @Column(name = "ISSUE_DATE")
    @Temporal(TemporalType.TIMESTAMP)
    public Date getIssueDate() {
        return issueDate;
    }

    public void setIssueDate(Date issueDate) {
        this.issueDate = issueDate;
    }
}

下面定义告诉JPA每个Person含有一个确定的IDCard:

@Entity
@Table(name = "T_PERSON")
public class Person {
    ...
    private IdCard idCard;
    ...

    @OneToOne
    @JoinColumn(name = "ID_CARD_ID")
    public IdCard getIdCard() {
        return idCard;
    }

一对多可以自行查阅文档。
下面举几个例子来看一下如何使用jpa查询

Page<User> findALL(Pageable pageable);
Page<User> findByUserName(String userName,Pageable pageable);

@Test
public void testPageQuery() throws Exception {
    int page=1,size=10;
    Sort sort = new Sort(Direction.DESC, "id");
    Pageable pageable = new PageRequest(page, size, sort);
    userRepository.findALL(pageable);
    userRepository.findByUserName("testName", pageable);
}

上面展示了一个简单的分页查询。
在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying.也可以根据需要添加 @Transactional 对事物的支持,查询超时的设置等

@Modifying
@Query("update User u set u.userName = ?1 where c.id = ?2")
int modifyByIdAndUserId(String  userName, Long id);
    
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);
  
@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
    User findByEmailAddress(String emailAddress);

通过查询学生获取班级信息

public class StudentBadDto {

    private Student stu;
    private Classroom cla;
    public StudentBadDto(Student stu, Classroom cla) {
        super();
        this.stu = stu;
        this.cla = cla;
    }

  //省略了getter和setter方法
}

查询代码

@Query("select new org.konghao.model.StudentBadDto(stu,cla) from Student stu,Classroom cla where stu.cid=cla.id")
public List<StudentDto> listBadStu();

@Test
public void testListStu() {
    List<StudentDto> sds = studentRepository.listBadStu();
    Assert.assertEquals(5, sds.size());
    Assert.assertEquals(2, sds.get(0).getStu().getId());
    Assert.assertEquals(1, sds.get(0).getCla().getId());
}

下面是发出的sql

image.png

下面给出一个完整的查询方案

public interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

查询方法的结果可以通过关键字first或者top来限制,它们可以交替使用。在top/firest后添加数字来表示返回最大的结果数。如果没有数字,则默认假定1作为结果大小。

User findFirstByOrderByLastnameAsc();

     User findTopByOrderByAgeDesc();

     Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

     Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

     List<User> findFirst10ByLastname(String lastname, Sort sort);

     List<User> findTop10ByLastname(String lastname, Pageable pageable);

下回有时间我会从底层对jap进行讲解,期待下一次把。。。

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

推荐阅读更多精彩内容