0

According to this doc, we can pass Class-based Projections into jpa api: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos

so I am trying to create dynamic jpa projections using BeanGenerator, but it seems not working... Anyone know how to generate dynamic projection in in spring data jpa?

     final Map<String, Class<?>> properties =
                new HashMap<String, Class<?>>();
        properties.put("name", String.class);
        properties.put("phone", String.class);

    final Class<?> beanClass = PojoGenerator.generate("com.logicbig.example.EmployeeName", properties);
       List list3 = employeeRepository.findByDept("IT", beanClass);



import javassist.*;

import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;

/**
 * copy from https://blog.javaforge.net/post/31913732423/howto-create-java-pojo-at-runtime-with-javassist
 *
 * @author blog.javaforge.net
 */
public class PojoGenerator {

    public static Class generate(String className, Map<String, Class<?>> properties) throws NotFoundException,
        CannotCompileException {

        ClassPool pool = ClassPool.getDefault();
        CtClass declaringClass = pool.makeClass(className);

        // add this to define a super class to extend
        // cc.setSuperclass(resolveCtClass(MySuperClass.class));

        // add this to define an interface to implement
        declaringClass.addInterface(resolveCtClass(Serializable.class));


        for (Entry<String, Class<?>> entry : properties.entrySet()) {
            String fieldName = entry.getKey();
            Class<?> fieldClass = entry.getValue();


            declaringClass.addField(new CtField(resolveCtClass(fieldClass), fieldName, declaringClass));

            // add getter
            declaringClass.addMethod(generateGetter(declaringClass, fieldName, fieldClass));

            // add setter
            declaringClass.addMethod(generateSetter(declaringClass, fieldName, fieldClass));


        }

        // add constructor
        generateConstructor(declaringClass, properties);

        return declaringClass.toClass();
    }

    private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass)
        throws CannotCompileException {

        String getterName = "get" + fieldName.substring(0, 1).toUpperCase()
            + fieldName.substring(1);

        StringBuffer sb = new StringBuffer();
        sb.append("public ").append(fieldClass.getName()).append(" ")
            .append(getterName).append("(){").append("return this.")
            .append(fieldName).append(";").append("}");
        return CtMethod.make(sb.toString(), declaringClass);
    }

    private static CtMethod generateSetter(CtClass declaringClass, String fieldName, Class fieldClass)
        throws CannotCompileException {

        String setterName = "set" + fieldName.substring(0, 1).toUpperCase()
            + fieldName.substring(1);

        StringBuffer sb = new StringBuffer();
        sb.append("public void ").append(setterName).append("(")
            .append(fieldClass.getName()).append(" ").append(fieldName)
            .append(")").append("{").append("this.").append(fieldName)
            .append("=").append(fieldName).append(";").append("}");
        return CtMethod.make(sb.toString(), declaringClass);
    }

    private static void generateConstructor(CtClass declaringClass, Map<String, Class<?>> properties)
        throws CannotCompileException {


        String constructorMethodName = declaringClass.getSimpleName();
        StringBuffer constructorMethod = new StringBuffer();
        StringBuffer constructorBody = new StringBuffer();

        constructorMethod.append("public ").append(constructorMethodName).append("(");
        for (Entry<String, Class<?>> entry : properties.entrySet()) {
            String fieldName = entry.getKey();
            Class<?> fieldClass = entry.getValue();

            // add constructor
            constructorMethod.append(fieldClass.getName()).append(" ").append(fieldName).append(",");
            constructorBody.append("this.").append(fieldName)
                .append("=").append(fieldName).append(";");
        }
        constructorMethod.deleteCharAt(constructorMethod.lastIndexOf(","));
        constructorMethod.append(") {");
        constructorMethod.append(constructorBody);
        constructorMethod.append("}");

        System.out.println(constructorMethod);
        CtConstructor constructor = CtNewConstructor.make(constructorMethod.toString(), declaringClass);
        declaringClass.addConstructor(constructor);

        CtConstructor defaultConstructor = CtNewConstructor.make("public " + constructorMethodName + "() {}", declaringClass);
        declaringClass.addConstructor(defaultConstructor);
    }

    private static CtClass resolveCtClass(Class clazz) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        return pool.get(clazz.getName());
    }
}
====== finding beanClass for IT dept =====
13:50:12.495 [main] DEBUG org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler - Creating new EntityManager for shared EntityManager invocation
13:50:12.495 [main] DEBUG org.hibernate.query.criteria.internal.CriteriaQueryImpl - Rendered criteria query -> select generatedAlias0 from Employee as generatedAlias0 where generatedAlias0.dept=:param0
13:50:12.496 [main] DEBUG org.hibernate.hql.internal.ast.QueryTranslatorImpl - parse() - HQL: select generatedAlias0 from com.logicbig.example.Employee as generatedAlias0 where generatedAlias0.dept=:param0
13:50:12.497 [main] DEBUG org.hibernate.hql.internal.ast.ErrorTracker - throwQueryException() : no errors
13:50:12.498 [main] DEBUG org.hibernate.hql.internal.ast.QueryTranslatorImpl - --- HQL AST ---
 \-[QUERY] Node: 'query'
    +-[SELECT_FROM] Node: 'SELECT_FROM'
    |  +-[FROM] Node: 'from'
    |  |  \-[RANGE] Node: 'RANGE'
    |  |     +-[DOT] Node: '.'
    |  |     |  +-[DOT] Node: '.'
    |  |     |  |  +-[DOT] Node: '.'
    |  |     |  |  |  +-[IDENT] Node: 'com'
    |  |     |  |  |  \-[IDENT] Node: 'logicbig'
    |  |     |  |  \-[IDENT] Node: 'example'
    |  |     |  \-[IDENT] Node: 'Employee'
    |  |     \-[ALIAS] Node: 'generatedAlias0'
    |  \-[SELECT] Node: 'select'
    |     \-[IDENT] Node: 'generatedAlias0'
    \-[WHERE] Node: 'where'
       \-[EQ] Node: '='
          +-[DOT] Node: '.'
          |  +-[IDENT] Node: 'generatedAlias0'
          |  \-[IDENT] Node: 'dept'
          \-[COLON] Node: ':'
             \-[IDENT] Node: 'param0'

13:50:12.498 [main] DEBUG org.hibernate.hql.internal.antlr.HqlSqlBaseWalker - select << begin [level=1, statement=select]
13:50:12.499 [main] DEBUG org.hibernate.hql.internal.ast.tree.FromElement - FromClause{level=1} : com.logicbig.example.Employee (generatedAlias0) -> employee0_
13:50:12.499 [main] DEBUG org.hibernate.hql.internal.ast.tree.FromReferenceNode - Resolved : generatedAlias0 -> employee0_.id
13:50:12.500 [main] DEBUG org.hibernate.hql.internal.ast.tree.FromReferenceNode - Resolved : generatedAlias0 -> employee0_.id
13:50:12.500 [main] DEBUG org.hibernate.hql.internal.ast.tree.DotNode - getDataType() : dept -> org.hibernate.type.StringType@6b63d445
13:50:12.500 [main] DEBUG org.hibernate.hql.internal.ast.tree.FromReferenceNode - Resolved : generatedAlias0.dept -> employee0_.dept
13:50:12.500 [main] DEBUG org.hibernate.hql.internal.antlr.HqlSqlBaseWalker - select : finishing up [level=1, statement=select]
13:50:12.500 [main] DEBUG org.hibernate.hql.internal.ast.HqlSqlWalker - processQuery() :  ( SELECT ( {select clause} employee0_.id ) ( FromClause{level=1} Employee employee0_ ) ( where ( = ( employee0_.dept employee0_.id dept ) ? ) ) )
13:50:12.509 [main] DEBUG org.hibernate.hql.internal.ast.util.JoinProcessor - Using FROM fragment [Employee employee0_]
13:50:12.509 [main] DEBUG org.hibernate.hql.internal.antlr.HqlSqlBaseWalker - select >> end [level=1, statement=select]
13:50:12.510 [main] DEBUG org.hibernate.hql.internal.ast.QueryTranslatorImpl - --- SQL AST ---
 \-[SELECT] QueryNode: 'SELECT'  querySpaces (Employee)
    +-[SELECT_CLAUSE] SelectClause: '{select clause}'
    |  +-[ALIAS_REF] IdentNode: 'employee0_.id as id1_0_' {alias=generatedAlias0, className=com.logicbig.example.Employee, tableAlias=employee0_}
    |  \-[SQL_TOKEN] SqlFragment: 'employee0_.dept as dept2_0_, employee0_.name as name3_0_, employee0_.phone as phone4_0_, employee0_.salary as salary5_0_'
    +-[FROM] FromClause: 'from' FromClause{level=1, fromElementCounter=1, fromElements=1, fromElementByClassAlias=[generatedAlias0], fromElementByTableAlias=[employee0_], fromElementsByPath=[], collectionJoinFromElementsByPath=[], impliedElements=[]}
    |  \-[FROM_FRAGMENT] FromElement: 'Employee employee0_' FromElement{explicit,not a collection join,not a fetch join,fetch non-lazy properties,classAlias=generatedAlias0,role=null,tableName=Employee,tableAlias=employee0_,origin=null,columns={,className=com.logicbig.example.Employee}}
    \-[WHERE] SqlNode: 'where'
       \-[EQ] BinaryLogicOperatorNode: '='
          +-[DOT] DotNode: 'employee0_.dept' {propertyName=dept,dereferenceType=PRIMITIVE,getPropertyPath=dept,path=generatedAlias0.dept,tableAlias=employee0_,className=com.logicbig.example.Employee,classAlias=generatedAlias0}
          |  +-[ALIAS_REF] IdentNode: 'employee0_.id' {alias=generatedAlias0, className=com.logicbig.example.Employee, tableAlias=employee0_}
          |  \-[IDENT] IdentNode: 'dept' {originalText=dept}
          \-[NAMED_PARAM] ParameterNode: '?' {name=param0, expectedType=org.hibernate.type.StringType@6b63d445}

13:50:12.510 [main] DEBUG org.hibernate.hql.internal.ast.ErrorTracker - throwQueryException() : no errors
13:50:12.510 [main] DEBUG org.hibernate.hql.internal.ast.QueryTranslatorImpl - HQL: select generatedAlias0 from com.logicbig.example.Employee as generatedAlias0 where generatedAlias0.dept=:param0
13:50:12.510 [main] DEBUG org.hibernate.hql.internal.ast.QueryTranslatorImpl - SQL: select employee0_.id as id1_0_, employee0_.dept as dept2_0_, employee0_.name as name3_0_, employee0_.phone as phone4_0_, employee0_.salary as salary5_0_ from Employee employee0_ where employee0_.dept=?
13:50:12.510 [main] DEBUG org.hibernate.hql.internal.ast.ErrorTracker - throwQueryException() : no errors
13:50:12.511 [main] DEBUG org.hibernate.SQL - select employee0_.id as id1_0_, employee0_.dept as dept2_0_, employee0_.name as name3_0_, employee0_.phone as phone4_0_, employee0_.salary as salary5_0_ from Employee employee0_ where employee0_.dept=?
13:50:12.512 [main] DEBUG org.hibernate.loader.Loader - Result set row: 0
13:50:12.515 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[com.logicbig.example.Employee#2]
13:50:12.520 [main] DEBUG org.hibernate.loader.Loader - Result set row: 1
13:50:12.521 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[com.logicbig.example.Employee#5]
13:50:12.521 [main] DEBUG org.hibernate.loader.Loader - Result set row: 2
13:50:12.521 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[com.logicbig.example.Employee#6]
13:50:12.521 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving associations for [com.logicbig.example.Employee#2]
13:50:12.522 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [com.logicbig.example.Employee#2]
13:50:12.522 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving associations for [com.logicbig.example.Employee#5]
13:50:12.522 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [com.logicbig.example.Employee#5]
13:50:12.522 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving associations for [com.logicbig.example.Employee#6]
13:50:12.522 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [com.logicbig.example.Employee#6]
13:50:12.522 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
13:50:12.523 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
Exception in thread "main" org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.logicbig.example.Employee] to type [com.logicbig.example.EmployeeName]
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174)
    at org.springframework.data.repository.query.ResultProcessor$ProjectingConverter.convert(ResultProcessor.java:293)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.lambda$and$0(ResultProcessor.java:213)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:152)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:141)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:125)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:590)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:578)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy34.findByDept(Unknown Source)
    at com.logicbig.example.ExampleClient.run(ExampleClient.java:74)
    at com.logicbig.example.ExampleMain.main(ExampleMain.java:12)
13:50:26.941 [pool-1-thread-1] DEBUG org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl - Connection pool now considered primed; min-size will be maintained


TonyLuo
  • 31
  • 4
  • I don't know the BeanGenerator you mention, but does it create classes that conform to what is laid out in the documentation? https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos – Jens Schauder Oct 05 '19 at 05:55
  • BeanGenerator is a util using cglib to generate pojo class, it should be the same with the normal DTOs. no idea why jpa cannot support the pojo which generated by cglib – TonyLuo Oct 07 '19 at 07:00

1 Answers1

0

Assuming the BeanGenerator generates typical Java Beans the class is probably missing the constructor with all the fields required:

If the store optimizes the query execution by limiting the fields to be loaded, the fields to be loaded are determined from the parameter names of the constructor that is exposed.

The quote is from https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348