欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java for Web学习笔记(一零三):Spring框架中使用JPA(3)JPA仓库

程序员文章站 2022-04-22 13:39:52
...

小例子

我们使用之前JPA小例子的数据库,有三个表Authors,Books和Publishers。先对表Author进行数据读写,读写无非就是增删改查,也就是CRUD。之前,我们已经如何进行Entity和数据库表格的对应,在此略过。

创建仓库接口

public interface AuthorRepository {
	Iterable<Author> getAll();
	Author get(long id);
	void add(Author author);
	void update(Author author);
	void delete(Author author);
	void delete(long id);
}

仓库接口的实现

@Repository
public class DefaultAuthorRepository implements AuthorRepository{
	//【注意】上一学习了解到:EntityManager不使用@Inject或者@Autowire,而是使用@PersistenceContext从proxy(SharedEntityManagerBean)中获取的在同一事务内唯一的entityManager对象。
	@PersistenceContext EntityManager entityManager;

	/* 例子使用的是Java Persistence Query Language (JPQL)。 JPQL不使用表名(已经进行了映射),而是使用类名 */
	@Override
	public Iterable<Author> getAll() {
		return this.entityManager.createQuery("SELECT a FROM Author a ORDER BY a.name",Author.class)
		                         .getResultList();
	}

	@Override
	public Author get(long id) {
		return this.entityManager.createQuery("SELECT a FROM Author a WHERE a.id = :id", Author.class)
		                         .setParameter("id", id)
		                         .getSingleResult();
	}

	@Override
	public void add(Author author) {
		this.entityManager.persist(author);		
	}

	@Override
	public void update(Author author) {
		this.entityManager.merge(author);		
	}

	@Override
	public void delete(Author author) {
		this.entityManager.remove(author);		
	}

	@Override
	public void delete(long id) {
		this.entityManager.createQuery("DELETE FROM Author a WHERE a.id = :id")
		                  .setParameter("id", id)
		                  .executeUpdate();		
	}
}

CRUD的通用代码

增删改查是最基本的数据访问,我们要为每个表实现这样的操作,代码是类似的,只是映射到不同的对象,下面介绍通用的代码。

通用接口定义:GenericRepository

I是index的类型,E为Entity的类型。

@Validated
public interface GenericRepository<I extends Serializable, E extends Serializable> {
	@NotNull Iterable<E> getAll();	
	E get(@NotNull I id);
	void add(@NotNull E entity);
	void update(@NotNull E entity);
	void delete(@NotNull E entity);
	void deleteById(@NotNull I id); //无法区别E和I,因此需采用不同的方法名字
}
相应的,仓库接口可以写为:
public interface BookRepository extends GenericRepository<Long, Book>{
	Book getByIsbn(@NotNull String isbn); //增加其他接口
}

public interface PublisherRepository extends GenericRepository<Long, Publisher>{ } //无增加

获取类型I和E:GenericBaseRepository

我们需要从BookRepository<Long, Book>中获取正确的Long,Book类型。下面是相关代码:
/* 在此给出一个通用的方法,我们并没有具体实现JPA,因为有可能是non-JPA的仓库。对于JPA,进一步使用GenericJPARepository来继承。
 * abstract表示不能使用实现这个接口。*/
public abstract class GenericBaseRepository<I extends Serializable, E extends Serializable> implements GenericRepository<I,E>{
	protected final Class<I> idClass;
	protected final Class<E> entityClass;
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public GenericBaseRepository(){
		/* 目的是通过((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()来获取<I,E>中I和E的具体类型。由于这个GenericBaseRepository被继承后,还可能多次再被继承,因此需要一直向上找到super class中带有具体ParameterizedType的class。代码中我们一直向上找到具体的指明参数类型。很显然当找到GenericBaseRepository就说明失败,无需向上走了。
                 * 即使我们只使用一次集成,并确保如此,也不能直接使用((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments(),因为对于Spring框架中的transaction proxying和exception translation也存在继承,直接一步获取类型可能会出错,因此需采用追溯的方式。 */
		Type genericSuperclass = this.getClass().getGenericSuperclass();
		while(!(genericSuperclass instanceof ParameterizedType)){
			if(!(genericSuperclass instanceof Class))
				throw new IllegalStateException("Unable to determine type arguments " +
						"because generic superclass neither parameterized type nor class.");
			if(genericSuperclass == GenericBaseRepository.class)
				throw new IllegalStateException("Unable to determine type " +
						"arguments because no parameterized generic superclass found");
			genericSuperclass = ((Class)genericSuperclass).getGenericSuperclass();
		}
		
		ParameterizedType type = (ParameterizedType)genericSuperclass;
		Type[] arguments = type.getActualTypeArguments();
		this.idClass = (Class<I>)arguments[0];
		this.entityClass = (Class<E>)arguments[1];
	}
}

通用的JPA实现:GenericJPARepository

public abstract class GenericJpaRepository<I extends Serializable, E extends Serializable> extends GenericBaseRepository<I, E>{	
	@PersistenceContext protected EntityManager entityManager;

	//【注意】query.from(this.entityClass)对应到SQL就是表格,由于没有明确类名,不能使用JPQL语句,而是使用 criteria API

	@Override
	public @NotNull Iterable<E> getAll() {
		CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
		CriteriaQuery<E> query = builder.createQuery(this.entityClass);
		return this.entityManager.createQuery(query.select(query.from(this.entityClass)))
		                         .getResultList();		
	}

	@Override
	public E get(@NotNull I id) {
		return this.entityManager.find(this.entityClass, id);
	}

	@Override
	public void add(@NotNull E entity) {
		this.entityManager.persist(entity);		
	}

	@Override
	public void update(@NotNull E entity) {
		this.entityManager.merge(entity);		
	}

	@Override
	public void delete(@NotNull E entity) {
		this.entityManager.remove(entity);		
	}

	/* 这里的属性名为id(每个类都如此),如果不是,需要deleteById(@NotNull I id, String idName)。 */
	@Override
	public void deleteById(@NotNull I id) {
		CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
		CriteriaDelete<E> query = builder.createCriteriaDelete(this.entityClass);
		this.entityManager.createQuery(query.where(builder.equal(query.from(this.entityClass).get("id"), id)))
		                  .executeUpdate();			
	}
}
最后的deleteById相当于

CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
CriteriaDelete<E> query = builder.createCriteriaDelete(this.entityClass);
Root<E> root = query.from(this.entityClass);           //读取entityClass对应的表
query.where(builder.equal(root.get("id"), id));        //读取的条件,
this.entityManager.createQuery(query).executeUpdate(); //因为query是createCriteriaDelete,因此执行就是delete
我们再看criteria API的例子:要求按name对应列进行升序排列,获取所有的书,代码如下:

...
Root<Book> root = query.from(Book.class);
return this.entityManager.createQuery(query.select(root).orderBy(builder.asc(root.get("name"))))
                         .getResultList();

具体的仓库实现

有了通用的实现,具体的实现就可以大大简化代码
@Repository
public class DefaultBookRepository extends GenericJpaRepository<Long, Book> implements BookRepository{
    @Override
    public Book getByIsbn(String isbn){
        CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
        CriteriaQuery<Book> query = builder.createQuery(this.entityClass);
        Root<Book> root = query.from(this.entityClass);
        return this.entityManager.createQuery(query.select(root).where(builder.equal(root.get("isbn"), isbn)))
                                 .getSingleResult();
    }
}

@Repository
public class DefaultPublisherRepository extends GenericJpaRepository<Long, Publisher> implements PublisherRepository{
}

相关链接:我的Professional Java for Web Applications相关文章