大致結構:
Person(人): id,name,age,bookId
Book(書):id,bookName
Author(作者):id,authorName,bookId
一個人 只有 一本書,一本書 有多個 作者,一個作者 只出 一本書;(可能舉例不好,明白就行)
Person ----> Book : OneToOne
Book ----> Author: OneToMany
期望達到的效果:
sql: select p.id id,p.name,b.bookName bookName from person p left join book b on p.bookId=b.id ;
不管這個person有沒有book都要列出這個person
如果是規范的hibernate實體對象(不知道怎么描述規范,大致是符合hibernate面向對象的表設計的實體類)
sql相應的hql: select p.id id,p.name,b.bookName bookName from person p left join p.book b with p.bookId=b.id ; (有很多寫法,但如果hibernate映射,我查到的說法是無法用hql寫left join)
@Entity
@Table
public class Person{ private String id; private String name; private String age; @OneToOne private Book book; //... }
@Entity
@Table public class Book { private String id; private String bookName; @OneToOne private Person person; @OneToMany private List<Author> authors; //... } @Entity
@Table public class Author{ private String id; private String authorName; @ManyToOne private Book book; //... }
如果是這樣,其實得到person就可以得到person的所有關聯對象。但,如果考慮極致的查詢效率、內存占用。那么(個人)感覺這做法不好。
較好的做法是,你要什么sql就返回什么指定的列映射到dto(所以,個人還是喜歡用ibatis/mybatis);
而且,現在遇到的問題是。實體對象Perosn中 private Book book; 寫成了 private String bookId;導致hql無法達到left的效果(在沒映射的關系下,反正沒找到能達到left join效果的hql)。
所以,就用hibernate的sql來實現。(也可以用jdbcTmplate.query(…),只是分頁是自己sql寫好的;返回到自定義對象只要實現RowMapper就可以)
Demo:
String sql = "select p.id id ,p.name name ,b.bookName bookName from person p left join book b on p.bookId=b.id"; SQLQuery q = ....; //q.addScalar("id",StringType.INSTANCE); //q.addScalar("name",StringType.INSTANCE); //q.addScalar("bookName",StringType.INSTANCE); q.setResultTransformer(Transformers.aliasToBean(Dto.class));
問題:
用的數據庫是oracle,所以導致默認獲得到的別名alias都轉成了大寫。但是,在dto中又是駝峰式的。
所以,在Transformers中會報exception:can not find setter;
因為,Transformers是根據alias反射找到setter。但alias都是大寫ID、NAME、BOOKNAME,但setter其實是setId、setName、setBookName。
解決:
1、sql中可以寫成 select p.id “id” ,p.name “name” ,b.bookName “bookName” from person p left join book b on p.bookId=b.id ; 即sql中alias都加上雙引號(oracle的規定),那么Transformers得到的alias不會被轉換成大寫。
2、設置addScalar(…),但個人覺得寫的太多而且不通用,每個都要寫。(最好加上類型)
3、重寫/擴展oracle的dialect;
4、我也在博問發求助了:Hibernate原生sql查詢多表返回自定義對象問題? ,又大致去查了下,在stackoverflow找到一個:mapping Hibernate query results to custom class? 重點看2L v.ladynev的回答,並且ta給了ta自己重寫的transformers fluent-hibernate;
其實ta的做法和我在看transformers源碼時想到的一樣(本來以為代碼量很少),既然是alias反射dto是找不到setter,那么就想辦法找到setter;
public Object transformTuple(Object[] tuple, String[] aliases) { Object result; try { if ( ! isInitialized ) { initialize( aliases ); //exception } else { check( aliases ); } result = resultClass.newInstance(); for ( int i = 0; i < aliases.length; i++ ) { if ( setters[i] != null ) { setters[i].set( result, tuple[i], null ); } } } catch ( InstantiationException e ) { throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() ); } catch ( IllegalAccessException e ) { throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() ); } return result; } private void initialize(String[] aliases) { PropertyAccessor propertyAccessor = new ChainedPropertyAccessor( new PropertyAccessor[] { PropertyAccessorFactory.getPropertyAccessor( resultClass, null ), PropertyAccessorFactory.getPropertyAccessor( "field" ) } ); this.aliases = new String[ aliases.length ]; setters = new Setter[ aliases.length ]; for ( int i = 0; i < aliases.length; i++ ) { String alias = aliases[ i ]; if ( alias != null ) { this.aliases[ i ] = alias; setters[ i ] = propertyAccessor.getSetter( resultClass, alias ); //exception: alias都是大寫,再深入看propertyAccessor.getSetter就知道是找不到setter } } isInitialized = true; }
// org.hibernate.property.BasicPropertyAccessor private static Method setterMethod(Class theClass, String propertyName) { //propertyName就是alias,大寫 BasicPropertyAccessor.BasicGetter getter = getGetterOrNull(theClass, propertyName); Class returnType = getter == null?null:getter.getReturnType(); Method[] methods = theClass.getDeclaredMethods(); Method potentialSetter = null; Method[] arr$ = methods; int len$ = methods.length; for(int i$ = 0; i$ < len$; ++i$) { Method method = arr$[i$]; String methodName = method.getName(); if(method.getParameterTypes().length == 1 && methodName.startsWith("set")) { String testStdMethod = Introspector.decapitalize(methodName.substring(3)); String testOldMethod = methodName.substring(3); // 都是dto的setter值,Id、Name、BookName if(testStdMethod.equals(propertyName) || testOldMethod.equals(propertyName)) { // 所以,此處if為false;method=null potentialSetter = method; if(returnType == null || method.getParameterTypes()[0].equals(returnType)) { return method; } } } } return potentialSetter; }
// 這是fluent-hibernate的源碼 ;com.github.fluent.hibernate.internal.util.reflection.ReflectionUtils /** * Try to find a class getter method by a property name. Don't check parent classes or * interfaces. * * @param classToCheck * a class in which find a getter * @param propertyName * a property name * @return the getter method or null, if such getter is not exist */ public static Method getClassGetter(Class<?> classToCheck, String propertyName) { PropertyDescriptor[] descriptors = getPropertyDescriptors(classToCheck); for (PropertyDescriptor descriptor : descriptors) { if (isGetter(descriptor, propertyName)) { return descriptor.getReadMethod(); } } return null; } private static boolean isGetter(PropertyDescriptor descriptor, String propertyName) { Method method = descriptor.getReadMethod(); return method != null && method.getParameterTypes().length == 0 && descriptor.getName().equalsIgnoreCase(propertyName); //忽略大小寫找到setter } private static PropertyDescriptor[] getPropertyDescriptors(Class<?> beanClass) { try { return Introspector.getBeanInfo(beanClass).getPropertyDescriptors(); } catch (IntrospectionException ex) { throw InternalUtils.toRuntimeException(ex); } }
我並沒去完整認真的去看fluent-hibernate,可能是因為自己也是想通過找到setter的去解決 + 作者回答時候也是說主要就是怎么找到setter,把BookName當作BOOKNAME看待:
Using a custom result transformer
Another way to solve the problem — using a result transformer that ignores method names case (treat
getFirstName()
asgetFIRSTNAME()
). You can write your own or use FluentHibernateResultTransformer. You will not need to use quotes and aliases (if you have column names equal to DTO names)
ps: 其實想看認真看下的,本來我想的不用寫多少代碼量。但發現作者寫了好多…
尚存疑問:
我記得我還看到的說法有說重寫或擴展oralce的dialect的。記得之前公司的項目框架有重寫oracle的dialect,貌似sql中的別名不會被轉換成全大寫。
具體不清楚,整天都是在寫垃圾的業務實現代碼,就知道叫加班,代碼一點質量都沒有,不好看懂、不好擴展、方法老舊,只是為了實現當前業務需求應付客戶。搞的下班了沒時間、也沒經理去深入一些框架的東西,煩躁…><!