最近用到的Spring Data Jpa 遇到很多问题,但是随着问题的解决我对于它有了更深入的了解。趁热打铁,我先记下来。
首先是要在Spring Data Jpa 中用到hibernate的Criteria的问题,Spring Data Jpa 为我们提供了接口JpaSpecificationExecutor。在我们的程序中继承该接口就可以实现Criteria的功能,这里不做过多详述。现在我们讲一个例子:我有一个项目,我有一个统一的接口,叫做BaseDao。我让他继承了两个接口PagingAndSortingRepository、JpaSpecificationExecutor为了什么呢,一方面指定我要查询的的数据也就是说我要什么东西,这功能归JpaSpecificationExecutor接口管;另一方面,希望对查出的数据进行分页和排序的处理。同时我在里面定义一个自己的方法名字叫:findAllByLikeName()。
@NoRepositoryBean
public interface BaseDao<T> extends PagingAndSortingRepository<T, String>,
JpaSpecificationExecutor<T> {
default Page<T> findAllByLikeName(String name, int page, int size) {
Specification<T> specification = new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
return cb.like(root.get("pname").as(String.class), "%" + name
+ "%");
}
};
Sort sort = new Sort(Direction.ASC, "pid");
Pageable pageable = new PageRequest(page - 1, size, sort);
Page pages = findAll(specification, pageable);
return pages;
}
}
这里需要注意@NoRepositoryBean这个是必不可少的,他的意思大概是不让repository也就是spring接管的容器来实现它,或者说放弃被实现的能力(因为我用的是spring Data Jpa进行接管的,默认他会自动来找到这个借口并且实现它)。这里可以先不管。不要以为我写错了,是的,这就是一个接口。只不过我用的JDK1.8,在里面加入了新特性,就是在接口中可以定义default类型的方法,并且实现它。感兴趣的同学可以自己去查看。page表示要显示的页数。下面-1操作是为了这里的分页是从第0页开始的,所以要-1。size是每页应该有多少数据。name是我自己传的参数,目的是为了在我的表中按关键字查人下面是我的数据库还有我的Person实体类(具体细节大家自己去看):
package com.icss.d3.goldenleaf.domain.entity.test;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "p_info")
public class Person {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String pid;
@Column(name = "P_age")
private int page;
@Column(name = "p_name", length = 100)
private String pname;
@ManyToOne(fetch = FetchType.EAGER)
@Cascade(value = { org.hibernate.annotations.CascadeType.ALL })
@JoinColumn(name = "parent")
private Person parentPerson;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "parentPerson")
@Cascade(value = { org.hibernate.annotations.CascadeType.ALL })
private List<Person> childPerson = new ArrayList<Person>();
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public Person getParentPerson() {
return parentPerson;
}
public void setParentPerson(Person parentPerson) {
this.parentPerson = parentPerson;
}
public List<Person> getChildPerson() {
return childPerson;
}
public void setChildPerson(List<Person> childPerson) {
this.childPerson = childPerson;
}
}
那么到这里我们就可以测试了,但是会发生错误Not an managed type: class java.lang.Object因为大家可以看到,我们在BaseDao中所有的都是泛型<T>,为了什么呢,为了节省日后的工作量,这里就不详细解释了,太浪费时间了。回来看,那么怎么解决这个问题呢,这个问题大概是说我们的类型没有匹配或者说压根我们就没指定一个具体的类型,指定会报错呀。那我们就需要给他指定类型,但是在哪呢,答案是:在子接口。
我们建立一个子接口,假设我们这个借口只针对Person进行操作的,那我们继承BaseDao接口同时指定父类的泛型类型如下代码:
package com.icss.d3.goldenleaf.dao;
import com.icss.d3.goldenleaf.domain.entity.test.Person;
public interface PersonBaseDao extends BaseDao<Person> {
}
如此我们就完美的解决了类型不匹配的问题了,那我们在测试的时候就可以直接构造PersonBaseDao的Repository了,而不去构造BaseDao。但是怎么告诉Spring这个消息呢,答案很简单就是我们之前提到的@NoRepositoryBean。然后我们进行测试了下面是我的测试代码:
package com.icss.d3.goldenleaf.dao;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.domain.Page;
import com.icss.d3.goldenleaf.config.SpringConfig;
import com.icss.d3.goldenleaf.domain.entity.DeviceCard;
import com.icss.d3.goldenleaf.domain.entity.test.Person;
public class TestDao {
private ApplicationContext context;
@Test
public void testRepository() {
context = new AnnotationConfigApplicationContext(SpringConfig.class);
PersonBaseDao repository = context.getBean(PersonBaseDao.class);
Page<Person> page = repository.findAllByLikeName("m", 1, 3);
List<Person> list = new ArrayList<Person>();
list = page.getContent();
for (Person p : list) {
System.out.println("person==========>name:" + p.getPname()
+ " ==========>age:" + p.getPage());
}
}
private Person buildPerson() {
Person person = new Person();
person.setPname("mahuichao");
person.setPage(23);
Person person1 = new Person();
person1.setPname("zhangxinhua");
person1.setPage(44);
Person person0 = new Person();
person0.setPname("zhangxiaoping");
person0.setPage(35);
Person person2 = new Person();
person2.setPname("fangyun");
person2.setPage(67);
Person person3 = new Person();
person3.setPname("xiaoyan");
person3.setPage(54);
Person person4 = new Person();
person4.setPname("sunwukong");
person4.setPage(13);
Person person5 = new Person();
person5.setPname("douzhanshen");
person5.setPage(44);
Person person6 = new Person();
person6.setPname("renhuang");
person6.setPage(27);
person1.setParentPerson(person);
person2.setParentPerson(person);
person3.setParentPerson(person);
person4.setParentPerson(person);
person5.setParentPerson(person);
person6.setParentPerson(person);
person0.setParentPerson(person);
List<Person> list = new ArrayList<Person>();
list.add(person6);
list.add(person5);
list.add(person4);
list.add(person3);
list.add(person2);
list.add(person1);
list.add(person0);
person.setChildPerson(list);
return person;
}
}
其实东西很简单,这个结构也很好。你没有实现类、没有过多操作,就简单完成了一个功能。如果你想加入方法,完全可以继续加入(按命名规范也好、自己default也好)都可以。同时如果你一定要有实现类的话你可以去试试,我是试过了很麻烦,对于泛型擦除这里问题太多。至少我感觉上面这真是一种不错的方案了。笨鸟奉上心得,希望对有些需要的人有帮助。如有大神路过还请奉上更好的建议,笨鸟谢过先。