Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

Working with Spring Data JPA repositories is easy if you use them the way they are intended to be used. One way that they don’t like to be used is with two different databases with the objects in the same directories. In a previous blog titled Spring Data JPA with multiple entity managers, I showed how to have multiple EntityManagers work with Spring Data JPA. As part of that, we had to annotate each method that used the repository with @Transactional. I didn’t like this and I was searching for a way to make it better.

I tried to filter the jpa:repositories with a regex, but that wouldn’t work.

I next tried filtering by annotation and it worked like a charm. Here is how I did it.

Create annotations

The annotations are very simple and there isn’t Spring specific about them. I created @ReadOnly and @ReadWrite annotations.

package com.javaninja.adw.spring;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * Annotation used to indicate that this should be picked up as a ReadOnly database operation, specifically for
 * Spring Data JPA repositories.
 * @author norrisshelton
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ReadOnly {

} 
package com.javaninja.adw.spring;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * Annotation used to indicate that this should be picked up as a ReadWrite database operation, specifically for
 * Spring Data JPA repositories.
 * @author norrisshelton
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ReadWrite {

} 

Annotate the repositories

I annotated the Read-only repository with @ReadOnly and annotated my Read-Write repository with @ReadWrite.

package com.javaninja.adw.example;


import com.javaninja.adw.spring.ReadOnly;

import org.springframework.data.jpa.repository.JpaRepository;


/**
 * Read-only Spring Data JPA Repository for Example objects.
 * @author norrisshelton
 */
@ReadOnly
public interface RoExampleRepository extends JpaRepository<ExampleEntity, Integer> {

    ExampleEntity findByAffiliateId(int affiliateId);
}
package com.javaninja.adw.example;


import com.javaninja.adw.spring.ReadWrite;

import org.springframework.data.jpa.repository.JpaRepository;


/**
 * Read-write Spring Data JPA Repository for Example objects.
 * @author norrisshelton
 */
@ReadWrite
public interface RwExampleRepository extends JpaRepository<ExampleEntity, Long> {

}

Repository declaration

Once I have that, I can move onto the Spring context configuration. I could then modify my jpa:repositories declaration to look like:

<jpa:repositories base-package="com.javaninja.adw"
                  entity-manager-factory-ref="roEntityManagerFactory"
                  transaction-manager-ref="roTransactionManager">
    <repository:include-filter type="annotation" expression="com.javaninja.adw.spring.ReadOnly"/>
</jpa:repositories>
<jpa:repositories base-package="com.javaninja.adw"
                  entity-manager-factory-ref="rwEntityManagerFactory"
                  transaction-manager-ref="rwTransactionManager">
    <repository:include-filter type="annotation" expression="com.javaninja.adw.spring.ReadWrite"/>
</jpa:repositories>

What does it look like when used

Once those changes are done, using it is as easy as any other usage. No more @Transactional annotations.

package com.javaninja.adw.example;


import com.javaninja.adw.ExampleModel;

import org.springframework.stereotype.Service;


/**
 * @author norris.shelton
 */
@Service
public class ExampleService {

    private final RoExampleRepository roExampleRepository;

    public ExampleService(RoExampleRepository roExampleRepository) {
        this.roExampleRepository = roExampleRepository;
    }

    /**
     * Gets an example model object.
     * @return populated example model
     */
    public ExampleModel getExampleModel(int affiliateId) {
        ExampleEntity exampleEntity = roExampleRepository.findByAffiliateId(affiliateId);
        ExampleModel exampleModel = new ExampleModel();
        exampleModel.setExampleProperty(exampleEntity.getName());
        return exampleModel;
    }
}

The rest of the Spring Context

The rest of the Spring Context configuration looks the same. There is two of anything. To prevent confusion, I pre-pended ro or rw to all of my objects.

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
<util:properties id="jpaProperties">
    <!--<prop key="hibernate.show_sql">false</prop>-->
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
</util:properties>
<bean id="rwEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
      p:dataSource-ref="rwAdwDataSource"
      p:packagesToScan="com.javaninja.adw"
      p:persistenceUnitName="rwPersistenceUnit"
      p:jpaVendorAdapter-ref="jpaVendorAdapter"
      p:jpaProperties-ref="jpaProperties"/>
<bean id="roEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
      p:dataSource-ref="roAdwDataSource"
      p:packagesToScan="com.javaninja.adw"
      p:persistenceUnitName="roPersistenceUnit"
      p:jpaVendorAdapter-ref="jpaVendorAdapter"
      p:jpaProperties-ref="jpaProperties"/>
<!--
    Transaction-related configuration
-->
<tx:annotation-driven/>
<bean id="rwTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="rwEntityManagerFactory"/>
    <qualifier value="rw"/>
</bean>
<bean id="roTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="roEntityManagerFactory"/>
    <qualifier value="ro"/>
</bean>

May 3rd, 2017

Posted In: Java, java ninja, Javaninja, Spring, Spring Data, Spring Data JPA

Leave a Comment

Spring Data JPA is my favorite way to interact with the database in Java. It makes short work of defining queries. I get to spend my time on business logic instead of keeping the compiler happy. I didn’t know how to use Spring-Data-JPA when I have two entity managers. It boiled down to a couple of minor changes. Normally you would have to add the following to your XML to enable the repositories.

<jpa:repositories base-package="com.twinspires.cam"/>

This doesn’t work because it can’t find the EntityManager named entityManager. That is because in my example, I have two entity managers. Here is how they are defined:

<bean id="rwEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
	  p:dataSource-ref="readWriteDataSource"
	  p:packagesToScan="com.javaninja.jpa"
	  p:jpaVendorAdapter-ref="jpaVendorAdapter"/>

<bean id="roEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
	  p:dataSource-ref="readOnlyDataSource"
	  p:packagesToScan="com.javaninja.jpa"
	  p:jpaVendorAdapter-ref="jpaVendorAdapter"/>

This requires that I have two jpa:repositories entries like the following:

<jpa:repositories base-package="com.twinspires.cam" entity-manager-factory-ref="roEntityManagerFactory"/>
<jpa:repositories base-package="com.twinspires.cam" entity-manager-factory-ref="rwEntityManagerFactory"/>

Now that I have two entity managers wired up over the same packages and classes, I have another problem. I need to indicate via my code which entity manager I need to use for this call. I do this by adding @Transactional with the name of my transactionManager. As an extra note, when I declare my transaction manager and add the entity manager that it works on, I also add a qualifier for it. This creates an alias for that transaction manager. This allows me to note that the ro transaction manager works on this code instead of having to say the roTransactionManager works on this code. Here is the transaction manager declaration:

<bean id="rwtransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="entityManagerFactory" ref="rwEntityManagerFactory" />
	<qualifier value="rw"/>
</bean>
<bean id="roTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="entityManagerFactory" ref="roEntityManagerFactory" />
	<qualifier value="ro"/>
</bean>

This makes it easy to use the alias in my repository:

@Transactional(value = "ro")
Customer findByThisAndThat(String this, String that);

February 27th, 2016

Posted In: Java, java ninja, Javaninja, Spring, Spring Data, Spring Data JPA

Leave a Comment

LinkedIn Auto Publish Powered By : XYZScripts.com