scribble

466300750.github.io

Blog GitHub

08 Jun 2018
JPA

Java Persistence API

java 对象与数据库表的映射称为 ORM (Object-relational mapping),JPA是ORM的一种方法。开发者可以通过JPA从关系型数据库中映射、存储、更新和获取数据为Java对象,或者反过来。

JPA是一个标准,有几种具体的实现:

  • Hibernate
  • EclipseLink
  • Apache OpenJPA

JPA使得开发者可以直接操作对象,而不用写SQL语句。也将JPA的实现称为 persistence provider。Java对象与数据库表之间的映射是通过 persistence metadata 实现的,它可以是注解,也可以是xml配置文件。

Entity

@Entity注解的实体,对应数据库中的一张表。每个实体必须定义一个主键,必须有一个非final的无参构造函数。

默认情况下类名即为表名,也可以不相同,通过注解@Table(name="NEWTABLENAME")映射。

Fields

JPA可以通过实例变量或者相应的getter、setter访问字段,JPA默认情况下可以访问所有的字段,如果某个字段不想被访问,可以通过注解@Transient实现。

默认情况下每个字段映射为表的列,可以通过注解@Column(name = "newColumnName")映射。

Relationship Mapping

JPA运行定义class直接的关系,比如一个class属于另一个class。一般有如下几种关系:

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

关系既可以是单向的也可以是双向的,在双向关系中需要通过属性”mappedBy”在另一个class中指明关系归属方,比如@ManyToMany(mappedBy="attributeOfTheOwningClass")

Entity Manager

Entity Manager 提供到数据库的操作。

有两种方式:

  1. Container-managed entity manger

    通过注解@PersistenceContext注入即可,需要spring jpa的支持

  2. Application-managed entity manager

    它通过EntityManagerFactory创建,该工厂类通过持久化单元配置,持久化单元通过META-INF下的persistence.xml描述。

EntityManagerFactory是线程安全的单例,EntityManager是每个线程有自己的实例。

YAZ(Yet Another Zeratul)

Zeratul的新版本,它提供了一个基础的BaseDao。帮我们实现了增删改查,用法如下:

public class UserDao extends BaseDao<User> {
    public UserDao() {
        super(User.class);
    }
    
    @PersistenceContext
    public void injectEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
    
    public List<User> findUserByName(String key) {
        return where(field("name").like(key), field("status").ne(DISABLED)).queryList();
    }
}

有时候会添加一个BaseDaoWrapper:

public abstract class BaseDaoWrapper<T> extends BaseDao<T> {
    protected BaseDaoWrapper(Class<T> prototype) {
        super(prototype);
    }

    @PersistenceContext
    public void injectEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
}

public class UserDao extends BaseDaoWrapper<User> {
    public UserDao() {
        super(User.class);
    }
    
    public List<User> findUserByName(String key) {
        return where(field("name").like(key), field("status").ne(DISABLED)).queryList();
    }
}

如果使用DDD,则会如下使用:

public class UserDao extends BaseDaoWrapper<User> {
    public UserDao() {
        super(User.class);
    }
}

public class UserRepository extends BaseRepository<User> {
    private UserDao userDao;

    public User find(String userId) {
        return userDao.idEquals(userId).querySingle();
    }
}

Querydsl

Querydsl 是一个类型安全的 Java 查询框架,支持 JPA, JDO, JDBC, Lucene, Hibernate Search 等标准。类型安全(Type safety)和一致性(Consistency)是它设计的两大准则。在 Spring Boot 中可以很好的弥补 JPA 的不灵活,实现更强大的逻辑。

Querydsl包含的模块有:

  • JPA
  • JDO
  • SQL
  • Lucene
  • Mongodb
  • Collections

引入querydsl插件,但不引入querydsl-jpa,可以使系统不引入jpa,通过SQL的方式实现存储层

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10"
    }
}

repositories {
    mavenCentral()
}

apply plugin: 'com.ewerk.gradle.plugins.querydsl'


configurations {
    querydsl.extendsFrom compileClasspath
}

dependencies {
	compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final'
	compile("com.querydsl:querydsl-sql:4.2.1")
}

ideaModule.dependsOn compileQuerydsl

querydsl {
    library = "com.querydsl:querydsl-apt"
    jpa = true
}

project.afterEvaluate {
    project.tasks.compileQuerydsl.options.compilerArgs = [
            "-proc:only",
            "-processor", project.querydsl.processors()
    ]
}

该插件会查找使用javax.persistence.Entity注解的域类型,并为它们生成对应的查询类型。

使用query-jpa,它包含以下几种用法

  1. 使用yaz + querydsl + spring jpa

     public class BaseRepositoryWrapper<T> extends BaseDao<T> {
    	
         protected BaseRepositoryWrapper(Class<T> prototype) {
             super(prototype);
         }
    	
         protected boolean isManaged(T entity) {
             return entityManager.contains(entity);
         }
    	
         protected <T> JPAQuery<T> from(EntityPath<T> from) {
             JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
             return jpaQueryFactory.selectFrom(from);
         }
    	
         protected <T> JPAQuery<T> select(Expression<T> what) {
             JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
             return jpaQueryFactory.select(what);
         }
    	
         @PersistenceContext
         public void injectEntityManager(EntityManager entityManager) {
             super.setEntityManager(entityManager);
         }
     }
    
  2. 使用querydsl + spring jpa

     public class QuerydslJpaSupport {
    	
         private EntityManager entityManager;
    	
         protected <T> JPAQuery<T> from(EntityPath<T> from) {
             JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
             return jpaQueryFactory.selectFrom(from);
         }
    	
         protected <T> JPAQuery<T> select(EntityPath<T> what) {
             JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
             return jpaQueryFactory.select(what);
         }
    	
         protected <T> JPAQuery<T> select(Expression<T> what) {
             JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
             return jpaQueryFactory.select(what);
         }
    	
         protected void store(Object entity) {
             if (entityManager.contains(entity)) {
                 entityManager.merge(entity);
                 return;
             }
             entityManager.persist(entity);
         }
    	
         protected JPADeleteClause delete(EntityPath<?> from) {
             return new JPADeleteClause(entityManager, from);
         }
    	
         protected EntityManager entityManager() {
             return entityManager;
         }
    	
         @PersistenceContext
         protected void setEntityManager(final EntityManager entityManager) {
             this.entityManager = entityManager;
         }
     }
    
  3. 要想使用spring data jpa提供的QueryDSL功能,很简单,直接继承接口即可。Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作接口,如下:

     public interface UserRepositoryDls extends JpaRepository<User, Integer>, QueryDslPredicateExecutor<User>{
     }
    

Spring Data Jpa

它是spring提供的一个轻量级ORM持久化框架,它并没有实现JPA,是一个管理工具,JPA的底层通常使用Hibrenate。

我们在实现的时候只需要声明持久化接口,该接口继承Repository或其子接口,Spring会生成实现代码:

public interface UserDao extends JpaRepository<User, Serializable>{
    List<User> findByNameLikeAndAgeGreaterThan(String firstName,Integer age);
}

spring 中接口的关系:

  1. Repository:仅仅只是一个标识,没有任何方法,方便Spring自动扫描识别
  2. CrudRepository:继承Repository,实现一组CRUD相关方法
  3. PagingAndStortingRepository:继承CrudRepository,实现一组分页排序相关方法
  4. JpaRepository:继承PagingAndStortingRepository,实现一组JPA规范方法

继承关系图

Spring 提供了一个便捷的方式使用 Querydsl,只需要在 Repository 中继承 QueryDslPredicateExecutor 即可:

@Repository public interface UserRepository extends JpaRepository<User, Long>, QueryDslPredicateExecutor<User> {}

然后就可以使用 UserRepository 无缝和 Querydsl 连接:

JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
// 基本查询
List<Person> persons = queryFactory.selectFrom(person)
  .where(
    person.firstName.eq("John"),
    person.lastName.eq("Doe"))
  .fetch();

// 排序
List<Person> persons = queryFactory.selectFrom(person)
  .orderBy(person.lastName.asc(), 
           person.firstName.desc())
  .fetch();

// 子查询
List<Person> persons = queryFactory.selectFrom(person)
  .where(person.children.size().eq(
    JPAExpressions.select(parent.children.size().max())
                  .from(parent)))
  .fetch();

// 投影
List<Tuple> tuples = queryFactory.select(
    person.lastName, person.firstName, person.yearOfBirth)
  .from(person)
  .fetch()
  .map(tuple -> {
                Map<String, String> map = new LinkedHashMap<>();
                map.put("name", tuple.get(user.name));
                map.put("email", tuple.get(user.email));
                return map;
            }).collect(Collectors.toList());

Spring JPA方法名解析步骤

query方法的命名规则,

  • 查询:find…By, read…By, query…By, count…By, and get…By;
  • 限制结果数量:findTopBy, findTop1By, findFirstBy, and findFirst1By
  • distinct:indTitleDistinctBy or findDistinctTitleBy

Til next time,
at 20:51

scribble