如何使用 Hibernate 按投影分组
How to group by projections with Hibernate
我需要查找属于特定组且位于特定地址的学生列表以及他们所在位置的 phone 号码。
我的主要问题是我无法检索每个学生的 phone 个数字作为集合。例如,如果我有 student1,student2。 Phone 位置 1 的学生 1 为 1111,位置 1 的学生 2 为 phone 2222 和 phone 3333,位置 2 的学生 2 为 phone 444。
假设我有
Student1 Alex group1 1111 Location1 Street1
Student3 Jack group1 93939 Location2 Street4
Student7 Joe group2 22223 Location4 Street8
Student2 John group1 2222 3333 Location1 Street1
Student2 John group1 4444 Location1 Street2
Student12 Mark group1 4423 Location9 Street9
带数据的示例输出
User asks for all students in group1 and location1
Student1 Alex Street1 phone 1111 distance30
Student2 John Street1 phones 22222,3333 distance30
Student2 John Street2 phone 4444 distance40
换句话说,我想要一份学生列表以及他们的位置和所选位置的 phones。
Hibernate returns 遵循我当前代码的错误消息
org.springframework.orm.hibernate4.HibernateSystemException:
IllegalArgumentException occurred while calling setter for property
[com.example.Address.phones (expected type = java.util.List)]; target
= [com.example.results.AllStudents@6deeac0], property value =
[11111111] setter of com.example.results.AllStudents.phones; nested
exception is IllegalArgumentException occurred while calling setter
for property [com.example.results.AllStudents.phones (expected type =
java.util.List)]; target = [com.example.results.AllStudents@6deeac0],
property value = [11111111]
学生
@Entity
public class Student implements java.io.Serializable {
private static final long serialVersionUID = -23949494858373847L;
@Id
@GeneratedValue
String id;
String name;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "student_groups", joinColumns = { @JoinColumn(name = "id", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "groupId", nullable = false, updatable = false) })
Set<Group> groups = new HashSet<Group>(0);
..
}
地址
@Entity
public class Address implements java.io.Serializable {
private static final long serialVersionUID = -274634747474623637L;
@Id
@GeneratedValue
String addId;
@Id
@ManyToOne
@JoinColumn(name = "id", nullable = false)
Student student;
@ManyToOne
@JoinColumn(name = "locId", nullable = false)
Location location;
double latitude;
double longitude;
String address;
@OneToMany(mappedBy = "phoneOwner", fetch = FetchType.EAGER)
Set<Phone> phones = new HashSet<Phone>();
String formula = "( 6371 * acos ( cos ( radians("
+ lat
+ ") ) * cos( radians( this_.latitude ) ) * cos( radians( this_.longitude ) - radians("
+ lan + ") ) +" + "sin ( radians(" + lat
+ ") ) * sin( radians( this_.latitude ) ) ) ) as distance";
Session session = sessionFactory.getCurrentSession();
ProjectionList pl = Projections
.projectionList()
.add(Projections.property("std.id").as("id"))
.add(Projections.property("std.name").as("name"))
.add(Projections.property("addr.address").as(
"address"))
.add(Projections.property("location.name").as("location"))
.add(Projections.property("location.city").as("city"))
.add(Projections.property("location.latitude").as("latitude"))
.add(Projections.property("location.longitude").as("longitude"))
.add(Projections.sqlProjection(formula,
new String[] { "distance" },
new Type[] { new DoubleType() }));
List<AllStudents> students = (List<AllStudents) session
.createCriteria(Address.class, "addr")
.createAlias("addr.student", "std")
.createAlias("std.groups", "group")
.createAlias("addr.location", "location")
.setProjection(pl)
.setFetchMode("group", FetchMode.JOIN)
.add(Restrictions.ilike("group.name", groupName))
.add(Restrictions.eq("location.id", locId))
.setResultTransformer(
new AliasToBeanNestedResultTransformer(AllStudents.class))
.list();
结果class
public class AllStudents {
List<String> phones;
...
}
AliasToBeanNestedResultTransformer
public class AliasToBeanNestedResultTransformer extends
AliasedTupleSubsetResultTransformer {
private static final long serialVersionUID = -8047276133980128266L;
private static final int TUPE_INDEX = 0;
private static final int ALISES_INDEX = 1;
private static final int FIELDNAME_INDEX = 2;
private static final PropertyAccessor accessor = PropertyAccessorFactory
.getPropertyAccessor("property");
private final Class<?> resultClass;
private Object[] entityTuples;
private String[] entityAliases;
private Map<String, Class<?>> fieldToClass = new HashMap<String, Class<?>>();
private Map<String, List<?>> subEntities = new HashMap<String, List<?>>();
private List<String> nestedAliases = new ArrayList<String>();
private Map<String, Class<?>> listFields = new HashMap<String, Class<?>>();
public boolean isTransformedValueATupleElement(String[] aliases,
int tupleLength) {
return false;
}
public AliasToBeanNestedResultTransformer(Class<?> resultClass) {
this.resultClass = resultClass;
}
public Object transformTuple(Object[] tuple, String[] aliases) {
handleSubEntities(tuple, aliases);
cleanParams(tuple, aliases);
ResultTransformer rootTransformer = new AliasToBeanResultTransformer(
resultClass);
Object root = rootTransformer.transformTuple(entityTuples,
entityAliases);
loadSubEntities(root);
cleanMaps();
return root;
}
private void handleSubEntities(Object[] tuple, String[] aliases)
throws HibernateException {
String fieldName = "";
String aliasName = "";
try {
for (int i = 0; i < aliases.length; i++) {
String alias = aliases[i];
if (alias.contains(".")) {
String[] sp = alias.split("\.");
StringBuilder aliasBuilder = new StringBuilder();
for (int j = 0; j < sp.length; j++) {
if (j == 0) {
fieldName = sp[j];
} else {
aliasBuilder.append(sp[j]);
aliasBuilder.append(".");
}
}
aliasName = aliasBuilder.substring(0,
aliasBuilder.length() - 1);
nestedAliases.add(alias);
manageEntities(fieldName, aliasName, tuple[i]);
}
}
} catch (NoSuchFieldException e) {
throw new HibernateException("Could not instantiate resultclass: "
+ resultClass.getName() + " for field name: " + fieldName
+ " and alias name:" + aliasName);
}
}
private Class<?> findClass(String fieldName) throws NoSuchFieldException,
SecurityException {
if (fieldToClass.containsKey(fieldName)) {
return fieldToClass.get(fieldName);
} else {
Class<?> subclass = resultClass.getDeclaredField(fieldName)
.getType();
if (subclass.equals(List.class) || subclass.equals(Set.class)) {
if (subclass.equals(List.class)) {
listFields.put(fieldName, LinkedList.class);
} else {
listFields.put(fieldName, HashSet.class);
}
Field field = resultClass.getDeclaredField(fieldName);
ParameterizedType genericType = (ParameterizedType) field
.getGenericType();
subclass = (Class<?>) genericType.getActualTypeArguments()[0];
}
fieldToClass.put(fieldName, subclass);
return subclass;
}
}
@SuppressWarnings("unchecked")
private void manageEntities(String fieldName, String aliasName,
Object tupleValue) throws NoSuchFieldException, SecurityException {
Class<?> subclass = findClass(fieldName);
if (!subEntities.containsKey(fieldName)) {
List<Object> list = new ArrayList<Object>();
list.add(new ArrayList<Object>());
list.add(new ArrayList<String>());
list.add(FIELDNAME_INDEX, subclass);
subEntities.put(fieldName, list);
}
((List<Object>) subEntities.get(fieldName).get(TUPE_INDEX))
.add(tupleValue);
((List<String>) subEntities.get(fieldName).get(ALISES_INDEX))
.add(aliasName);
}
private void cleanParams(Object[] tuple, String[] aliases) {
entityTuples = new Object[aliases.length - nestedAliases.size()];
entityAliases = new String[aliases.length - nestedAliases.size()];
for (int j = 0, i = 0; j < aliases.length; j++) {
if (!nestedAliases.contains(aliases[j])) {
entityTuples[i] = tuple[j];
entityAliases[i] = aliases[j];
++i;
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void loadSubEntities(Object root) throws HibernateException {
try {
for (String fieldName : subEntities.keySet()) {
Class<?> subclass = (Class<?>) subEntities.get(fieldName).get(
FIELDNAME_INDEX);
ResultTransformer subclassTransformer = new AliasToBeanNestedResultTransformer(
subclass);
Object subObject = subclassTransformer.transformTuple(
((List<Object>) subEntities.get(fieldName).get(0))
.toArray(),
((List<Object>) subEntities.get(fieldName).get(1))
.toArray(new String[0]));
Setter setter = accessor.getSetter(resultClass, fieldName);
if (listFields.containsKey(fieldName)) {
Class<?> collectionClass = listFields.get(fieldName);
Collection subObjectList = (Collection) collectionClass
.newInstance();
subObjectList.add(subObject);
setter.set(root, subObjectList, null);
} else {
setter.set(root, subObject, null);
}
}
} catch (Exception e) {
throw new HibernateException(e);
}
}
private void cleanMaps() {
fieldToClass = new HashMap<String, Class<?>>();
subEntities = new HashMap<String, List<?>>();
nestedAliases = new ArrayList<String>();
listFields = new HashMap<String, Class<?>>();
}
}
AliasToBeanNestedResultTransformer 支持嵌套 DTO,但不支持 DTO 集合。
您可以将 AllStudent
DTS 更改为:
public class AllStudents {
Student student;
String phone;
Location location;
public AllStudents(Student student, String phone, Location location) {
this.student = student;
this.phone = phone;
this.location = location;
}
public Student getStudent() {
return student;
}
public String getPhone() {
return phone;
}
public Location getLocation() {
return location;
}
}
并且需要添加一个StudentDTO
来保存聚合结果:
public class StudentDTO {
private final Student student;
private String location;
private List<String> phones = new ArrayList<>();
public StudentDTO(Student student) {
this.student = student;
}
public Student getStudent() {
return student;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public List<String> getPhones() {
return phones;
}
}
现在,当您 运行 您的查询时,您会得到一个 AllStudents 列表:
List<AllStudents> allStudents = ...
您只需像这样分组:
LinkedHashMap<Long, StudentDTO> studentMap = new LinkedHashMap<>();
for(AllStudents all : allStudents) {
StudentDTO studentDTO = studentMap.get(all.getStudent().getId());
if(studentDTO == null) {
studentDTO = new StudentDTO(all.getStudent());
studentMap.put(all.getStudent().getId(), studentDTO);
}
if(all.getPhone() != null) {
studentDTO.getPhones().add(all.getPhone());
}
studentDTO.setLocation(all.getLocation());
}
List<StudentDTO> studentDTOs = new ArrayList<>(studentMap.values());
我需要查找属于特定组且位于特定地址的学生列表以及他们所在位置的 phone 号码。
我的主要问题是我无法检索每个学生的 phone 个数字作为集合。例如,如果我有 student1,student2。 Phone 位置 1 的学生 1 为 1111,位置 1 的学生 2 为 phone 2222 和 phone 3333,位置 2 的学生 2 为 phone 444。
假设我有
Student1 Alex group1 1111 Location1 Street1
Student3 Jack group1 93939 Location2 Street4
Student7 Joe group2 22223 Location4 Street8
Student2 John group1 2222 3333 Location1 Street1
Student2 John group1 4444 Location1 Street2
Student12 Mark group1 4423 Location9 Street9
带数据的示例输出
User asks for all students in group1 and location1
Student1 Alex Street1 phone 1111 distance30
Student2 John Street1 phones 22222,3333 distance30
Student2 John Street2 phone 4444 distance40
换句话说,我想要一份学生列表以及他们的位置和所选位置的 phones。
Hibernate returns 遵循我当前代码的错误消息
org.springframework.orm.hibernate4.HibernateSystemException:
IllegalArgumentException occurred while calling setter for property
[com.example.Address.phones (expected type = java.util.List)]; target
= [com.example.results.AllStudents@6deeac0], property value =
[11111111] setter of com.example.results.AllStudents.phones; nested
exception is IllegalArgumentException occurred while calling setter
for property [com.example.results.AllStudents.phones (expected type =
java.util.List)]; target = [com.example.results.AllStudents@6deeac0],
property value = [11111111]
学生
@Entity
public class Student implements java.io.Serializable {
private static final long serialVersionUID = -23949494858373847L;
@Id
@GeneratedValue
String id;
String name;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "student_groups", joinColumns = { @JoinColumn(name = "id", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "groupId", nullable = false, updatable = false) })
Set<Group> groups = new HashSet<Group>(0);
..
}
地址
@Entity
public class Address implements java.io.Serializable {
private static final long serialVersionUID = -274634747474623637L;
@Id
@GeneratedValue
String addId;
@Id
@ManyToOne
@JoinColumn(name = "id", nullable = false)
Student student;
@ManyToOne
@JoinColumn(name = "locId", nullable = false)
Location location;
double latitude;
double longitude;
String address;
@OneToMany(mappedBy = "phoneOwner", fetch = FetchType.EAGER)
Set<Phone> phones = new HashSet<Phone>();
String formula = "( 6371 * acos ( cos ( radians("
+ lat
+ ") ) * cos( radians( this_.latitude ) ) * cos( radians( this_.longitude ) - radians("
+ lan + ") ) +" + "sin ( radians(" + lat
+ ") ) * sin( radians( this_.latitude ) ) ) ) as distance";
Session session = sessionFactory.getCurrentSession();
ProjectionList pl = Projections
.projectionList()
.add(Projections.property("std.id").as("id"))
.add(Projections.property("std.name").as("name"))
.add(Projections.property("addr.address").as(
"address"))
.add(Projections.property("location.name").as("location"))
.add(Projections.property("location.city").as("city"))
.add(Projections.property("location.latitude").as("latitude"))
.add(Projections.property("location.longitude").as("longitude"))
.add(Projections.sqlProjection(formula,
new String[] { "distance" },
new Type[] { new DoubleType() }));
List<AllStudents> students = (List<AllStudents) session
.createCriteria(Address.class, "addr")
.createAlias("addr.student", "std")
.createAlias("std.groups", "group")
.createAlias("addr.location", "location")
.setProjection(pl)
.setFetchMode("group", FetchMode.JOIN)
.add(Restrictions.ilike("group.name", groupName))
.add(Restrictions.eq("location.id", locId))
.setResultTransformer(
new AliasToBeanNestedResultTransformer(AllStudents.class))
.list();
结果class
public class AllStudents {
List<String> phones;
...
}
AliasToBeanNestedResultTransformer
public class AliasToBeanNestedResultTransformer extends
AliasedTupleSubsetResultTransformer {
private static final long serialVersionUID = -8047276133980128266L;
private static final int TUPE_INDEX = 0;
private static final int ALISES_INDEX = 1;
private static final int FIELDNAME_INDEX = 2;
private static final PropertyAccessor accessor = PropertyAccessorFactory
.getPropertyAccessor("property");
private final Class<?> resultClass;
private Object[] entityTuples;
private String[] entityAliases;
private Map<String, Class<?>> fieldToClass = new HashMap<String, Class<?>>();
private Map<String, List<?>> subEntities = new HashMap<String, List<?>>();
private List<String> nestedAliases = new ArrayList<String>();
private Map<String, Class<?>> listFields = new HashMap<String, Class<?>>();
public boolean isTransformedValueATupleElement(String[] aliases,
int tupleLength) {
return false;
}
public AliasToBeanNestedResultTransformer(Class<?> resultClass) {
this.resultClass = resultClass;
}
public Object transformTuple(Object[] tuple, String[] aliases) {
handleSubEntities(tuple, aliases);
cleanParams(tuple, aliases);
ResultTransformer rootTransformer = new AliasToBeanResultTransformer(
resultClass);
Object root = rootTransformer.transformTuple(entityTuples,
entityAliases);
loadSubEntities(root);
cleanMaps();
return root;
}
private void handleSubEntities(Object[] tuple, String[] aliases)
throws HibernateException {
String fieldName = "";
String aliasName = "";
try {
for (int i = 0; i < aliases.length; i++) {
String alias = aliases[i];
if (alias.contains(".")) {
String[] sp = alias.split("\.");
StringBuilder aliasBuilder = new StringBuilder();
for (int j = 0; j < sp.length; j++) {
if (j == 0) {
fieldName = sp[j];
} else {
aliasBuilder.append(sp[j]);
aliasBuilder.append(".");
}
}
aliasName = aliasBuilder.substring(0,
aliasBuilder.length() - 1);
nestedAliases.add(alias);
manageEntities(fieldName, aliasName, tuple[i]);
}
}
} catch (NoSuchFieldException e) {
throw new HibernateException("Could not instantiate resultclass: "
+ resultClass.getName() + " for field name: " + fieldName
+ " and alias name:" + aliasName);
}
}
private Class<?> findClass(String fieldName) throws NoSuchFieldException,
SecurityException {
if (fieldToClass.containsKey(fieldName)) {
return fieldToClass.get(fieldName);
} else {
Class<?> subclass = resultClass.getDeclaredField(fieldName)
.getType();
if (subclass.equals(List.class) || subclass.equals(Set.class)) {
if (subclass.equals(List.class)) {
listFields.put(fieldName, LinkedList.class);
} else {
listFields.put(fieldName, HashSet.class);
}
Field field = resultClass.getDeclaredField(fieldName);
ParameterizedType genericType = (ParameterizedType) field
.getGenericType();
subclass = (Class<?>) genericType.getActualTypeArguments()[0];
}
fieldToClass.put(fieldName, subclass);
return subclass;
}
}
@SuppressWarnings("unchecked")
private void manageEntities(String fieldName, String aliasName,
Object tupleValue) throws NoSuchFieldException, SecurityException {
Class<?> subclass = findClass(fieldName);
if (!subEntities.containsKey(fieldName)) {
List<Object> list = new ArrayList<Object>();
list.add(new ArrayList<Object>());
list.add(new ArrayList<String>());
list.add(FIELDNAME_INDEX, subclass);
subEntities.put(fieldName, list);
}
((List<Object>) subEntities.get(fieldName).get(TUPE_INDEX))
.add(tupleValue);
((List<String>) subEntities.get(fieldName).get(ALISES_INDEX))
.add(aliasName);
}
private void cleanParams(Object[] tuple, String[] aliases) {
entityTuples = new Object[aliases.length - nestedAliases.size()];
entityAliases = new String[aliases.length - nestedAliases.size()];
for (int j = 0, i = 0; j < aliases.length; j++) {
if (!nestedAliases.contains(aliases[j])) {
entityTuples[i] = tuple[j];
entityAliases[i] = aliases[j];
++i;
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void loadSubEntities(Object root) throws HibernateException {
try {
for (String fieldName : subEntities.keySet()) {
Class<?> subclass = (Class<?>) subEntities.get(fieldName).get(
FIELDNAME_INDEX);
ResultTransformer subclassTransformer = new AliasToBeanNestedResultTransformer(
subclass);
Object subObject = subclassTransformer.transformTuple(
((List<Object>) subEntities.get(fieldName).get(0))
.toArray(),
((List<Object>) subEntities.get(fieldName).get(1))
.toArray(new String[0]));
Setter setter = accessor.getSetter(resultClass, fieldName);
if (listFields.containsKey(fieldName)) {
Class<?> collectionClass = listFields.get(fieldName);
Collection subObjectList = (Collection) collectionClass
.newInstance();
subObjectList.add(subObject);
setter.set(root, subObjectList, null);
} else {
setter.set(root, subObject, null);
}
}
} catch (Exception e) {
throw new HibernateException(e);
}
}
private void cleanMaps() {
fieldToClass = new HashMap<String, Class<?>>();
subEntities = new HashMap<String, List<?>>();
nestedAliases = new ArrayList<String>();
listFields = new HashMap<String, Class<?>>();
}
}
AliasToBeanNestedResultTransformer 支持嵌套 DTO,但不支持 DTO 集合。
您可以将 AllStudent
DTS 更改为:
public class AllStudents {
Student student;
String phone;
Location location;
public AllStudents(Student student, String phone, Location location) {
this.student = student;
this.phone = phone;
this.location = location;
}
public Student getStudent() {
return student;
}
public String getPhone() {
return phone;
}
public Location getLocation() {
return location;
}
}
并且需要添加一个StudentDTO
来保存聚合结果:
public class StudentDTO {
private final Student student;
private String location;
private List<String> phones = new ArrayList<>();
public StudentDTO(Student student) {
this.student = student;
}
public Student getStudent() {
return student;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public List<String> getPhones() {
return phones;
}
}
现在,当您 运行 您的查询时,您会得到一个 AllStudents 列表:
List<AllStudents> allStudents = ...
您只需像这样分组:
LinkedHashMap<Long, StudentDTO> studentMap = new LinkedHashMap<>();
for(AllStudents all : allStudents) {
StudentDTO studentDTO = studentMap.get(all.getStudent().getId());
if(studentDTO == null) {
studentDTO = new StudentDTO(all.getStudent());
studentMap.put(all.getStudent().getId(), studentDTO);
}
if(all.getPhone() != null) {
studentDTO.getPhones().add(all.getPhone());
}
studentDTO.setLocation(all.getLocation());
}
List<StudentDTO> studentDTOs = new ArrayList<>(studentMap.values());