Spring 学习DB优化Java技术升华

JPA规范

2019-07-20  本文已影响5人  风少侠

[TOC]

ORM思想

ORM全称Object Relational Mapping,即对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。

通俗点讲,用来把对象映射到基于sql的关系模型数据库结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的sql语句打交道,只需简单的操作实体对象的属性和方法。ORM技术是在对象和关系之间提供了一条桥梁,前台的对象型数据和数据库中的关系型的数据通过这个桥梁来相互转化 。

JPA规范

JPA(Java持久化API)是一种Java应用程序接口规范,描述java应用中关系数据的管理,充当面向对象的领域模型和关系数据库系统之间的桥梁。

Application code ---> JPA ---->实现JPA规范的框架(比如hibernate、spring data jpa) ----> 数据库

graph LR

A[Application code] --> |jpa规范| B(hibernate)
B -->|jdbc| C(mysql数据库)

hibernate框架

hibernate是一个开源的对象关系映射框架,它对jdbc进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成sql语句,自动执行,使得java程序员可以随心所欲的使用对象编程思维来操纵数据库。

入门案例

1、创建项目工程,导入相关依赖

    compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.4.3.Final'
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
    compile group: 'org.hibernate', name: 'hibernate-c3p0', version: '5.4.3.Final'

2、配置jpa的核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <!--必须配置persistence-unit节点-->
    <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
        <!--jpa实现方式-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <!--配置实体类-->
        <class>com.lxf.User</class>
        <properties>
            <!--数据库信息-->
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="crystal1024"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/study?serverTimezone=GMT"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>

            <!--jpa实现方的配置信息-->
            <!--日志显示sql语句-->
            <property name="hibernate.show_sql" value="true"/>
            <!--
            自动创建数据库的方式:
                create:程序运行时创建数据库表,如果表存在,先删除再创建
                update:程序运行时创建数据库表,如果表存在,不会创建,表有改动的话会更新
                none:不会创建表
            -->
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>

3、编写实体类POJO

public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;//0未知1男2女
    private String address;
    private String phone;
    ...省略getter setter
}

4、配置实体类和表,类中属性和表中字段的映射关系

import javax.persistence.*;

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    @Column(name = "name")
    private String name;
    @Column(name = "age")
    private Integer age;
    @Column(name = "sex")
    private Integer sex;//0未知1男2女
    @Column(name = "address")
    private String address;
    @Column(name = "phone")
    private String phone;
    ...省略getter setter
}

5、jpa操作数据库

    @Test
    public void testSave(){
        //1.加载配置文件,获取工厂对象
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("myJpa");
        //2.创建实体管理器
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        //3.获取事务对象
        EntityTransaction transaction = entityManager.getTransaction();
        //4.开启事务
        transaction.begin();
        try {
            //5.相关CRUD操作
            User user = new User();
            user.setName("tom");
            user.setAge(18);
            user.setSex(1);
            //保存
            entityManager.persist(user);
            //6.提交事务
            transaction.commit();
        }catch (Exception e){
            //6.回滚事务
            transaction.rollback();
            throw new RuntimeException("出异常啦");
        }finally {
            //7.释放资源
            entityManager.close();
            entityManagerFactory.close();
        }
    }

控制台打印出的sql日志:

Hibernate: create table user (id integer not null auto_increment, address varchar(255), age integer, name varchar(255), phone varchar(255), sex integer, primary key (id)) engine=InnoDB

Hibernate: insert into user (address, age, name, phone, sex) values (?, ?, ?, ?, ?)

优化EntityManagerFactory的创建

上面有提到,EntityManagerFactory的创建比较浪费资源,而且它是线程安全对象。所以一般会以静态代码块的形式创建一个公共的EntityManagerFactory对象。

public class JPAUtil {
    private static EntityManagerFactory factory;
    
    static {
        factory = Persistence.createEntityManagerFactory("myJpa");
    }
    
    public static EntityManager getEntityManager(){
        return factory.createEntityManager();
    }
}

此时我们的代码会变成这样:

    @Test
    public void testSave(){
        EntityManager entityManager = JPAUtil.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            User user = new User();
            user.setName("tom");
            user.setAge(18);
            user.setSex(1);
            entityManager.persist(user);
            transaction.commit();
        }catch (Exception e){
            transaction.rollback();
            throw new RuntimeException("出异常啦");
        }finally {
            entityManager.close();
        }
    }

数据库操作

插入操作

User user = new User();
user.setName("tom");
user.setAge(18);
user.setSex(1);
//接收要插入的对象
entityManager.persist(user);

删除操作

//先查询出我们要删除的对象
User user = entityManager.getReference(User.class, 1);
//接收要删除的对象
entityManager.remove(user);

更新操作

User user = entityManager.getReference(User.class, 2);
user.setName("lili");
//接收要修改的对象
entityManager.merge(user);

查询操作

内置API

根据主键查询单个:

 /**
  * 参数:
  *  1. class对象:查询数据结果需要包装的实体类类型的字节码
  *  2. 主键  这里是id
  */
 User user = entityManager.find(User.class, 1);

 /**
  * 参数:
  *  1. class对象:查询数据结果需要包装的实体类类型的字节码
  *  2. 主键  这里是id
  */
 User user = entityManager.getReference(User.class, 1);

find和getReference两者的区别:

语句查询

创建查询对象Query

jpa提供了一系列create方法来获取一个Query对象进行复杂查询。

jpql全称Java Persistence Query Language,java持久化查询语言,它和sql的语法很像,不过操作的是类和属性。

String jpql = "select u.name from User u";
//String jpql = "select name from User";//和上面语句效果是一样的
Query query = entityManager.createQuery(jpql);
@Entity
@Table(name = "user")
@NamedQuery(name = "queryName",query = "select u.name from User u")
public class User {
    ...
}

Query query = entityManager.createNamedQuery("queryName");
Query query = entityManager.createNativeQuery("select u.name from user as u");

常用查询操作

排序
统计
分页
String jpql = "from User";
Query query = entityManager.createQuery(jpql);
query.setFirstResult(1);
query.setMaxResults(10);
条件查询

Query的常用方法

Criteria查询

Criteria查询是在jpa 2.0的版本加入的一个更加符合面向对象思维的类型安全的查询方式。

首先介绍几个概念:

举个列子:select * from User where id > 3 and age >18

通俗点讲,select * from User属于Root,where属于CriteriaQuery,查询方式,id > 3 and age >18属于CriteriaBuilder构建出来的查询条件,这么讲可能不是很准确,但很容易理解。

简单翻译一下上面的语句:

public class JpaCriteriaTest {
    @Test
    public void testCriteria(){
        EntityManager entityManager = JPAUtil.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            //拿到CriteriaBuilder、CriteriaQuery、Root三个对象
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
            Root<User> root = criteriaQuery.from(User.class);

            //构建过滤条件
            Predicate idCondition = criteriaBuilder.greaterThan(root.get("id"), 3);
            Predicate ageCondition = criteriaBuilder.greaterThan(root.get("age"), 18);
            Predicate predicate = criteriaBuilder.and(idCondition, ageCondition);

            //组合查询
            criteriaQuery.where(predicate);

            //查询并获取结果
            TypedQuery<User> typedQuery = entityManager.createQuery(criteriaQuery);
            List<User> resultList = typedQuery.getResultList();//得到id>3的所有数据
            resultList.forEach(new Consumer<User>() {
                @Override
                public void accept(User user) {
                    System.out.println(user);
                }
            });

            transaction.commit();
        }catch (Exception e){
            transaction.rollback();
            e.printStackTrace();
            throw new RuntimeException("出异常啦");
        }finally {
            entityManager.close();
        }
    }
}

如果我们只想查询某几个字段,可以使用CriteriaQuery<Tuple>:

//拿到CriteriaBuilder、CriteriaQuery、Root三个对象
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleQuery = criteriaBuilder.createTupleQuery();
Root<User> root = tupleQuery.from(User.class);
tupleQuery.multiselect(root.get("name"),root.get("age"));

//构建过滤条件
Predicate idCondition = criteriaBuilder.greaterThan(root.get("id"), 3);
Predicate ageCondition = criteriaBuilder.greaterThan(root.get("age"), 18);
Predicate predicate = criteriaBuilder.and(idCondition, ageCondition);

//组合查询
tupleQuery.where(predicate);

//查询并获取结果
TypedQuery<Tuple> typedQuery = entityManager.createQuery(tupleQuery);
List<Tuple> resultList = typedQuery.getResultList();//得到id>3的所有数据
resultList.forEach(new Consumer<Tuple>() {
   @Override
   public void accept(Tuple tuple) {
      System.out.println(tuple.get(0,String.class));
       System.out.println(tuple.get(1,Integer.class));
   }
});

多表关系映射

一对多

另外,一对一其实就是一种特殊的一对多。

案例

一个student表,一个school表,一个学校可以有很多学生,一个学生只属于一个学校。

@Entity
@Table
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Integer studentId;
    @Column
    private String name;
    @Column
    private Double score;
    @Column
    private Integer sex;
    @Column(name = "school_id",insertable = false,updatable = false)//外键字段
    private Integer schoolId;

    @ManyToOne(targetEntity = School.class)//配置多对一关系
    @JoinColumn(name = "school_id",referencedColumnName = "schoolId")//配置外键
    private School school;
    ...
}

@Entity
@Table
public class School {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer schoolId;
    @Column
    private String name;
    @Column
    private String address;

//    @OneToMany(targetEntity = Student.class)//声明关系,一对多
//    @JoinColumn(name = "school_id",referencedColumnName = "schoolId")//配置外键
    @OneToMany(mappedBy = "school")//Student里面已经配置了映射关系,表示参照Student里面的school
    private List<Student> students = new ArrayList<>();
    ...
}

保存测试

public class JpaOneToManyTest {

    @Test
    public void testSave(){
        EntityManager entityManager = JPAUtil.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            Student student = new Student();
            student.setName("石昊");
            School school = new School();
            school.setName("天神书院");

            //创建关系
            student.setSchool(school);
//            school.getStudents().add(student);//两句代码都可以创建关系

            entityManager.persist(school);//先保存主表,如果先保存从表的话最好需要多一个update操作设置从表外键字段值
            entityManager.persist(student);

            transaction.commit();
        }catch (Exception e){
            transaction.rollback();
            e.printStackTrace();
            throw new RuntimeException("出异常啦");
        }finally {
            entityManager.close();
        }
    }
}

删除测试

级联操作

操作一个对象的同时操作它的关联对象。(先操作关联对象)

我们使用cascade来配置级联关系:

@ManyToOne(targetEntity = School.class,cascade = CascadeType.ALL)
@JoinColumn(name = "school_id",referencedColumnName = "schoolId")

级联保存

Student student = new Student();
student.setName("叶凡");
School school = new School();
school.setName("荒古禁地");

//创建关系
 student.setSchool(school);

//这里只保存从表,主表会被级联保存
entityManager.persist(school);

级联删除

@OneToMany(mappedBy = "school",cascade = CascadeType.ALL)
private List<Student> students = new ArrayList<>();
School school = entityManager.find(School.class, 10);
entityManager.remove(school);//此时主表和从表中相关的数据都会删除

多对多

案例

一个developer表,一个language表,一个开发者可以会多种语言,一种语言也会有很多开发者会。

中间表:中间表中最少应该由两个字段组成,这两个字段作为外键指向两张表的主键,又组成了联合主键。

@Entity
@Table
public class Developer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Integer developerId;
    @Column
    private String name;

    @ManyToMany(targetEntity = Language.class)
    @JoinTable(name = "middle_develop_language",
            joinColumns = @JoinColumn(name = "develop_id", referencedColumnName = "developerId"),
            inverseJoinColumns = @JoinColumn(name = "language_id", referencedColumnName = "languageId"))
    private List<Language> languages = new ArrayList<>();
    ...
}


@Entity
@Table
public class Language {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Integer languageId;
    @Column
    private String des;

//    @ManyToMany(targetEntity = Developer.class)
//    @JoinTable(name = "middle_develop_language",
//            joinColumns = @JoinColumn(name = "language_id", referencedColumnName = "languageId"),
//            inverseJoinColumns = @JoinColumn(name = "develop_id", referencedColumnName = "developerId"))
    @ManyToMany(mappedBy = "languages")//放弃维护权,参照对方的映射关系
    private List<Developer> developers = new ArrayList<>();
    private List<Developer> developers = new ArrayList<>();
    ...
}

保存测试

public class JpaManyToManyTest {

    @Test
    public void testSave(){
        EntityManager entityManager = JPAUtil.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            Developer developer = new Developer();
            developer.setName("lxf");
            Language language = new Language();
            language.setDes("java");

            //创建关系
            developer.getLanguages().add(language);

            entityManager.persist(developer);
            entityManager.persist(language);

            transaction.commit();
        }catch (Exception e){
            transaction.rollback();
            e.printStackTrace();
            throw new RuntimeException("出异常啦");
        }finally {
            entityManager.close();
        }
    }
}

级联操作

级联的配置和一对多是一样的,只需要在@ManyToMany配上cascade = CascadeType.ALL属性。

    @ManyToMany(targetEntity = Language.class,cascade = CascadeType.ALL)
    @JoinTable(name = "middle_develop_language",
            joinColumns = @JoinColumn(name = "develop_id", referencedColumnName = "developerId"),
            inverseJoinColumns = @JoinColumn(name = "language_id", referencedColumnName = "languageId"))
    private List<Language> languages = new ArrayList<>();

级联保存

Developer developer = new Developer();
developer.setName("lxf");
Language language = new Language();
language.setDes("java");

developer.getLanguages().add(language);

entityManager.persist(developer);

级联删除

Developer developer = entityManager.find(Developer.class, 1);
entityManager.remove(developer);//注意此时只能通过developer级联删除,因为只有Developer配置了cascade

对象导航查询

对象导航查询并不是一种新的查询方式,而是在多表关系中,通过查询某个对象,可以直接通过get得到相关联的数据信息,这是jpa的一种特性,但需要注意:

    @OneToMany(mappedBy = "school",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    private List<Student> students = new ArrayList<>();

通过一方查多方

以上面的一对多案例为例,通过查询一个学校,得到该学校的所有学生。

public class JpaObjectQueryTest {
    @Test
    public void test(){
        EntityManager entityManager = JPAUtil.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
//            School school = entityManager.find(School.class, 5);
            School school = entityManager.getReference(School.class, 5);
            //可以直接通过get方法来得到和其相关的信息,如果懒加载,则此时会发送sql语句查询关联表信息
            List<Student> students = school.getStudents();
            System.out.println(students.size());
            students.forEach(new Consumer<Student>() {
                @Override
                public void accept(Student student) {
                    System.out.println(student);
                }
            });

            transaction.commit();
        }catch (Exception e){
            transaction.rollback();
            e.printStackTrace();
            throw new RuntimeException("出异常啦");
        }finally {
            entityManager.close();
        }
    }
}

通过多方查一方

            Student student = entityManager.find(Student.class, 5);
//            Student student = entityManager.getReference(Student.class, 5);
            School school = student.getSchool();
            System.out.println(school);

Demo源码地址

https://github.com/lunxinfeng/jpa

上一篇下一篇

猜你喜欢

热点阅读