4

I'm trying to write some JUnit tests for a Spring 2.0.7 application.

In my application context XML file I have this bean:

<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" 
      id="entityManagerFactory"> ... </bean>

In my DAOs, I just annotate the setter for the entity manager and Spring does all the magic:

@PersistenceContext
public void setEntityManager(EntityManager em) {
    this.em = em;
}

In Spring 2.5+, I would just annotate my test class with @RunWith(SpringJUnit4ClassRunner.class) and @ContextConfiguration(locations = {"/applicationContext-test.xml"}). But my Spring version is too old, there isn't a SpringJUnit4ClassRunner. Therefore, no Spring magic for my JUnit tests. Instead, I'm doing it all myself:

// Load the XML file myself
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocations(new String[] { "classpath:applicationContext-test.xml" });
appContext.refresh();

// Retrieve the entity manager factory
Object obj = appContext.getBean("entityManagerFactory");
// ***** The following line throws an exception: *****
LocalContainerEntityManagerFactoryBean aux = (LocalContainerEntityManagerFactoryBean) obj;
EntityManagerFactory emf = aux.getObject();

// Instantiate the EM and inject it into the DAOs
EntityManager em = emf.createEntityManager();
myDAO.setEntityManager(em);

I get a ClassCastException: Cannot cast $Proxy23 ... exception when I try to cast obj to aux. However, if I set a breakpoint in Eclipse and inspect obj, it actually is an instance of LocalContainerEntityManagerFactoryBean.

I have tried this approach which I've seen in quite a few questions here. However, AopUtils.isJdkDynamicProxy(obj) returns false. Furthermore, even trying to cast (Advised) obj throws the same exception.

I have also tried casting obj to FactoryBean, which is one of the interfaces implemented by LocalContainerEntityManagerFactoryBean... and yes, it throws the same exception. Which is confusing, because almost every answer to "class cast exception proxy" say that "you can only cast to interfaces, not to classes".

So, my questions are:

  • Is there any way to fix the code I posted? (Yes, I wish I could upgrade Spring! But no, I can't).
  • OR: is there any other way in which I can instantiate the entity manager using the EMF defined in the XML file? Please note that I can't do just this:
emf = Persistence.createEntityManagerFactory("myPersistenceUnit");
em = emf.createEntityManager();

That works fine, but I lose the <aop:config> and <tx:advice> entries defined in the applicationContext-test.xml file.

MWiesner
  • 8,868
  • 11
  • 36
  • 70
AJPerez
  • 3,435
  • 10
  • 61
  • 91
  • Did you check if `public Object getBean(String name, Class requiredType)` works in your case? see: https://docs.spring.io/spring/docs/2.0.x/javadoc-api/org/springframework/context/support/AbstractApplicationContext.html – MWiesner Jan 31 '18 at 16:39
  • Thanks @MWiesner, I just tried. It fails with: `BeanNotOfRequiredTypeException: Bean named 'entityManagerFactory' must be of type [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean], but was actually of type [$Proxy23]`. So.. basically the same :) – AJPerez Jan 31 '18 at 16:54
  • 1
    Could you try to cast it to (the interface type) `javax.persistence.EntityManagerFactory`? Basically, this should be working as the proxy instance should implement this interface. :) – MWiesner Jan 31 '18 at 16:55
  • According to https://docs.spring.io/spring-framework/docs/2.0.x/api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html this class implements `EntityManagerFactoryInfo` which offers you `getNativeEntityManagerFactory()`. With this intermediate step, you should be able to retrieve the underlying `emf`. If this turns out to work, I can formulate an long version for an answer to your question. – MWiesner Jan 31 '18 at 17:03
  • 1
    Unbelieveable. Casting to EntityManagerFactory did work!!!! Which was what I needed in the first place, I just thought "nah, impossible.. it's a different class, I need to use the getObject method to get the emf instance". **Thanks a lot**!! – AJPerez Jan 31 '18 at 17:07

1 Answers1

4

You can use another version of the getBean(...) method to define the the required type more strictly, see official Spring 2.0 JavaDoc:

public Object getBean(String name, Class requiredType)

which states:

requiredType - type the bean must match. Can be an interface or superclass of the actual class, or null for any match. For example, if the value is Object.class, this method will succeed whatever the class of the returned instance.

Thus, to solve your problem with casts, you need to implement it this way:

// Retrieve the entity manager factory
EntityManagerFactory emf = (EntityManagerFactory) 
   appContext.getBean("entityManagerFactory", javax.persistence.EntityManagerFactory.class);

With this, you will directly retrieve a "neutral" EMF, compatible with the interface as described by the JPA specification. At runtime, this will be a proxy object, yet you can use it the same way as you originally intended:

// Instantiate the EM and inject it into the DAOs
EntityManager em = emf.createEntityManager();
// ... whatever comes here - most likely: doing some CRUD operations...

Hope it helps.

MWiesner
  • 8,868
  • 11
  • 36
  • 70
  • I actually did `emf = (EntityManagerFactory) appContext.getBean("entityManagerFactory");`, without specifying the target class. But yes, it helped, massively. Thanks again! :) – AJPerez Jan 31 '18 at 17:30
  • This should work in most (e.g., in your) cases. In more complex Spring configurations (involving multiple module contexts) however, several references can cause you headache again. Therefore, IMHO, it is better to be explicit here. – MWiesner Jan 31 '18 at 17:32
  • @AJPerez Addendum: Whoever is responsible for not updating the Spring version of the project you are working on here... please tell him/her: This is a **critical** task! It's not just a pain for dev guys who have to find ways to keep it running / testable, but also in terms of **IT-Sec**... -.- – MWiesner Jan 31 '18 at 17:42