how can i inject EntityManager(jpa) with Mockito?
I wanna bind Mockito.spy(UserService.class) to guice injector.
but UserService.class has EntityManager for query execution.
When installing 'JunitServiceModule' in guice injector, EntityManager is not found.
See below for error details
com.google.inject.CreationException: Unable to create injector, see the following errors:
1) Error in custom provider, java.lang.NullPointerException
while locating com.google.inject.persist.jpa.JpaPersistService
while locating javax.persistence.EntityManager
My code is below.
(The test code just made the error situation similar.)
(actually 'EntityManager' is located in UserRepository... It's just example!)
(#Transactional is belong to guice)
public class UserServiceTest {
#Inject
private UserService userService;
#Before
public void setUp() {
Injector injector = new TestBuilder().init();
injector.initMembers(this);
Mockito.doReturn(10).when(userService).getEntityCount(UserEntity.class);
}
#Test
public void test() {
assertEquals(10, userService.getEntityCount(UserEntity.class));
}
}
public class TestBuilder {
public TestBuilder() {
}
public Injector init() {
Injector injector = Guice.createInjector(
new TestDBInjectModule("test"),
new JunitServiceModule()
);
}
}
public class TestDBInjectModule extends AbstractModule {
private String unitName;
public TestDBInjectModule(String unitName) {
this.unitName = unitName;
}
#Override
protected void configure() {
install(new JpaPersistModule(unitName));
bind(JpaInitializer.class).asEagerSingleton();
}
#Singleton
private static class JpaInitializer {
#Inject
public JpaInitializer(final PersistService persistService) {
persistService.start();
}
}
}
public class JunitServiceModule extends AbstractModule {
#Override
protected void configure() {
bind(UserService.class).toInstance(Mockito.spy(UserService.class));
}
}
public class UserService {
#Inject
private EntityManager entityManager;
public UserService {} // <-- throw NullPointerException!!! since EntityManager
#Transactional
public void addUser(User user) {
return entityManager.persist(user);
}
public Number getCount() {
return entityManager.createQuery("select count(*) from user", Number.class).getSingleResult();
}
}
I'm trying to setup a generic DAO and service for a project i'm working at, it all seems to be working except I have to manually set the class<T> clazz everywhere I instantiate a repository, which is ok for the ones I have an entire class for (such as the clientRepository one below).
I tried to do the #Autowired setClazz(Class<T> clazz) in the AbstractRepository to solve it but it just doesn't work, when I debug findById(Long id), the clazz is null.
Is there any way to do this or do I really have to call the setClazz everywhere?
AbstractRepository
public abstract class AbstractRepository<T extends IGenericEntity> {
#PersistenceUnit(unitName = "clima_PU")
private EntityManagerFactory entityManagerFactory;
private Session session;
private Class<T> clazz;
#PersistenceContext
protected EntityManager entityManager;
#Autowired
public void setClazz(Class<T> clazz) {
this.clazz = clazz;
}
public T findByID(Long id) {
return (T) entityManager.find(clazz, id);
}
}
GenericRepository
#Repository
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class GenericRepository<T extends AbstractEntity> extends AbstractRepository<T> implements IGenericRepository<T> {
}
ClientRepository
public class ClientRepository extends GenericRepository<ClientEntity> {
/* Custom Queries for Clients */
}
I have generic abstract class AbstractBaseEntityGenericDao which contains #Autowired field. It worked perfectly until I had to write a unit test for it, to not duplicate the same code inside all tests for classes which extends it. And now I'm thinking...Is it possible to write a unit/integration test for such class?
#Repository
#Transactional
public abstract class AbstractBaseEntityGenericDao<T extends BaseEntity> {
private Class<T> classInstance;
private SessionFactory sessionFactory;
#Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public final void setClassInstance(Class<T> clasInstance) {
this.classInstance = clasInstance;
}
public void create(#NonNull T entity) {
Session session = sessionFactory.getCurrentSession();
session.save(entity);
}
public Optional<T> find(long id) {
Session session = sessionFactory.getCurrentSession();
return Optional.ofNullable(session.get(classInstance, id));
}
public void update(#NonNull T entity) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(entity);
}
public void remove(#NonNull Long id) throws EntityNotFoundException {
Session session = sessionFactory.getCurrentSession();
session.remove(session.load(classInstance, id));
}
public void remove(#NonNull T entity) {
Session session = sessionFactory.getCurrentSession();
session.remove(entity);
}
}
The reason this is difficult is because generally you should not be doing this. The abstract class should have no knowledge of how its child creates SessionFactory. so instead it should look something like:
#Repository
#Transactional
public abstract class AbstractBaseEntityGenericDao<T extends BaseEntity> {
...
protected SessionFactory sessionFactory;
...
}
Now you CANNOT directly unit test a abstract class as it can not be instantiated. you can however stub it out in a unit test, and test that stub. The stub in turn will have a constructor for the protected field which we can mock out in the unit test. In the end it would look like:
public class AbstractBaseEntityGenericDaoTest {
private class AbstractClassStub extends AbstractBaseEntityGenericDao {
public AbstractClassStub(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
#Override
public void create(BaseEntity entity) {
super.create(entity);
}
#Override
public Optional find(long id) {
return super.find(id);
}
#Override
public void update(BaseEntity entity) {
super.update(entity);
}
#Override
public void remove(#NonNull Long id) throws EntityNotFoundException {
super.remove(id);
}
#Override
public void remove(BaseEntity entity) {
super.remove(entity);
}
}
#Mock
SessionFactory sessionFactory;
private AbstractClassStub abstractClassStub;
#Before
public void before() {
sessionFactory = mock(SessionFactory.class);
abstractClassStub = new AbstractClassStub(sessionFactory);
}
#Test
public void testWhatever() {
abstractClassStub.find(1); //or whatever
}
}
Context:
I used queryDSL in API controller which binds query to the database get. Currently, I have two tables with OneToOne relationship, and we can call them Table A and Table B. If there are 3 rows in A and 2 rows in B, when I get list A with some conditions, and the queryDSL will generate query SQL like A CROSS JOIN B WHERE A.id=B.a_id, but it will miss one item in A. Thus, I am going to implement custom repository to support change join type when generating the SQL statement. The following is some parts of my code:
(Table A is named LabelTask and Table B is named AuditTask)
and generated sql segment is
from
label_task labeltask0_ cross
join
audit_task audittask1_
where
labeltask0_.id=audittask1_.label_task
Is there something wrong with my code or is there another good solution for this situation?
JoinDescriptor.java
public class JoinDescriptor {
public final EntityPath path;
public final JoinType type;
private JoinDescriptor(EntityPath path, JoinType type) {
this.path = path;
this.type = type;
}
public static JoinDescriptor innerJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.INNERJOIN);
}
public static JoinDescriptor join(EntityPath path) {
return new JoinDescriptor(path, JoinType.JOIN);
}
public static JoinDescriptor leftJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.LEFTJOIN);
}
public static JoinDescriptor rightJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.RIGHTJOIN);
}
public static JoinDescriptor fullJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.FULLJOIN);
}
}
JoinFetchCapableQueryDslJpaRepositoryFactoryBean.java
public class JoinFetchCapableQueryDslJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
public JoinFetchCapableQueryDslJpaRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new JoinFetchCapableQueryDslJpaRepositoryFactory(entityManager);
}
private static class JoinFetchCapableQueryDslJpaRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
public JoinFetchCapableQueryDslJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new JoinFetchCapableRepositoryImpl<>(getEntityInformation(metadata.getDomainType()), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return JoinFetchCapableRepository.class;
}
}
}
JoinFetchCapableRepository.java
#NoRepositoryBean
public interface JoinFetchCapableRepository<T, ID extends Serializable> extends
JpaRepository<T, ID>,
QuerydslPredicateExecutor<T> {
Page<T> findAll(Predicate predicate,
Pageable pageable,
JoinDescriptor... joinDescriptors);
}
JoinFetchCapableRepositoryImpl.java
public class JoinFetchCapableRepositoryImpl <T, ID extends Serializable>
extends QuerydslJpaRepository<T, ID>
implements JoinFetchCapableRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public JoinFetchCapableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public JoinFetchCapableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager,
EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable, JoinDescriptor... joinDescriptors) {
JPQLQuery countQuery = createQuery(predicate);
JPQLQuery query = querydsl.applyPagination(pageable, createFetchQuery(predicate, joinDescriptors));
Long total = countQuery.fetchCount();
List<T> content = total > pageable.getOffset()
? query.fetch()
: Collections.emptyList();
return new PageImpl<>(content, pageable, total);
}
private JPQLQuery createFetchQuery(Predicate predicate, JoinDescriptor... joinDescriptors) {
JPQLQuery query = querydsl.createQuery(path);
for(JoinDescriptor joinDescriptor: joinDescriptors)
join(joinDescriptor, query);
return (JPQLQuery) query.where(predicate);
}
private JPQLQuery join(JoinDescriptor joinDescriptor, JPQLQuery query) {
switch(joinDescriptor.type) {
case DEFAULT:
throw new IllegalArgumentException("cross join not supported");
case INNERJOIN:
query.innerJoin(joinDescriptor.path);
break;
case JOIN:
query.join(joinDescriptor.path);
break;
case LEFTJOIN:
query.leftJoin(joinDescriptor.path);
break;
case RIGHTJOIN:
query.rightJoin(joinDescriptor.path);
break;
case FULLJOIN:
query.join(joinDescriptor.path);
break;
}
return query.fetchAll();
}
}
JpaConfig.java
#Configuration
#EnableJpaRepositories(
basePackages = "com.some.company.service.repository",
repositoryFactoryBeanClass =JoinFetchCapableQueryDslJpaRepositoryFactoryBean.class)
public class JpaConfig {}
LabelTaskRepository
#Repository
public interface LabelTaskRepository extends
JoinFetchCapableRepository<LabelTask, String>,
QuerydslBinderCustomizer<QLabelTask> {
#Override
default void customize(QuerydslBindings bindings, QLabelTask qLabelTask){
this.bindQueryByTaskType(bindings, qLabelTask);
this.bindQueryByCreatedDateRange(bindings, qLabelTask);
// TODO: should remove this when task could be able to assign
bindings.excluding(qLabelTask.status);
}
...
}
Result:
when I launch the spring application, It will return the following error message:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'auditTaskController' defined in file [/.../some/company/service/controllers/AuditTaskController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'auditTaskService' defined in file [/.../some/company/service/AuditTaskService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'auditTaskRepository': Invocation of init method failed; nested exception is java.lang.IllegalStateException: No suitable constructor found on interface some.company.utils.JoinFetchCapableRepository to match the given arguments: [class org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation, class com.sun.proxy.$Proxy182]. Make sure you implement a constructor taking these
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:733)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:198)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1266)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1123)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:548)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
No suitable constructor found on interface
some.company.utils.JoinFetchCapableRepository to match the given
arguments: [class
org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation,
class com.sun.proxy.$Proxy182].
Based on the exception message, JoinFetchCapableRepositoryImpl needs a constructor which receives two parameters: JpaMetamodelEntityInformation, $Proxy182.
I added a constructor like this:
public JoinFetchCapableRepositoryImpl(
JpaMetamodelEntityInformation<T, ID> entityInformation,
EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
After this, It works for me and is able to change join type for query dsl
I have an abstract DAO:
public abstract class AbstractJpaDAO<T extends Serializable> implements I_AbstractJpaDAO<T> {
private Class<T> clazz;
#PersistenceContext
protected EntityManager entityManager;
public final void setClazz(final Class<T> clazzToSet) {
this.clazz = clazzToSet;
}
#Override
public T findOne(final long id) {
return entityManager.find(clazz, id);
}
#Override
public List<T> findAll() {
return entityManager.createQuery("from " + clazz.getName()).getResultList();
}
#Override
public void create(final T entity) {
entityManager.persist(entity);
}
#Override
public T update(final T entity) {
return entityManager.merge(entity);
}
#Override
public void delete(final T entity) {
entityManager.remove(entity);
}
#Override
public void deleteById(final long entityId) {
final T entity = findOne(entityId);
delete(entity);
}
}
I then extend this DAO in each DAO implementation (code not included) but header something like:
public class UserDAOImpl extends AbstractJpaDAO <User> implements UserDAO {
.....
With each entity, I work with a base interface type, for this example,let's call it UserDAO, and have an associated implementation, let's call it, UserDAOIMPL. To avoid having to define the same methods each Interface to each DAO. As in this longwinded example, i.e :
public interface UserDAO {
User findOne(long id);
List<User> findAll();
void create(User user);
User update(User user);
void delete(User user);
void deleteById(long userID);
User findUserByUserName(String name);
EntityManager returnEntityManager();
}
I would like to instead create a base interface, that all DAOs can extend.
public interface I_AbstractJpaDAO<T> {
.....
}
and then use this in each DAO interface.
public interface userDAO extends I_AbstractJpaDAO<T> { .....
However, I'm getting problems with "both methods have erasure, but neither overrides the other". Something to do with the Serialization thing I suspect. Can anyone help me please?
It means that, your base interface and abstract interface have the methods with the same signature and different return type.
Try this:
public interface userDAO extends I_AbstractJpaDAO<User> { .....