Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

In a previous blog, I showed how to use JPA to read and write to a database with JPA. Spring Batch – reading and writing JPA. We prefer to use Spring Data Repositories instead of using the JPA directly. Thankfully, there is a way to use a Spring Data Repository directly with Spring Batch.

RepositoryItemWriter

This is how a RepositoryItemWriter is declared. It follows the normal ItemWriter pattern. It needs a Spring Data Repository and to know what method should be called on the repository, save in this case.

@Bean
public RepositoryItemWriter<WagerActivity> writer() {
    RepositoryItemWriter<WagerActivity> writer = new RepositoryItemWriter<>();
    writer.setRepository(wagerActivityRepository);
    writer.setMethodName("save");
    return writer;
}

Spring Batch 4 has the concept of Builders. This is an example of what the Builder for this repository should look like:

return new RepositoryItemWriterBuilder<WagerActivity>()
                .methodName("save").repository(wagerActivityRepository).build();

Repository Declaration

A repository is an interface that surrounds an Entity. My interface is as follows:

public interface WagerActivityRepository extends JpaRepository<WagerActivity, Integer> {
}

Save Method

I don’t like hard-coded Strings, so I figured out how to get the save method name from Spring Data. The first thing I had to do was get the RepositoryMetadata. I created a DefaultRepositoryMetadata with my repository to instantiate the object. I then instantiated a DefaultCrudMethods with the repository metadata. Once I had that, I could get the save method and the name that should be used to access it. I removed the need for a hard-coded String, but I don’t think it is worth the effort. Too bad there isn’t a Enum.

@Bean
public RepositoryMetadata repositoryMetadata() {
    return new DefaultRepositoryMetadata(WagerActivityRepository.class);
}
@Bean
public RepositoryItemWriter<WagerActivity> writer() {
    DefaultCrudMethods defaultCrudMethods = new DefaultCrudMethods(repositoryMetadata());
    RepositoryItemWriter<WagerActivity> writer = new RepositoryItemWriter<>();
    writer.setRepository(wagerActivityRepository);
    writer.setMethodName(defaultCrudMethods.getSaveMethod().getName());
    return writer;
}

December 7th, 2017

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

Leave a Comment

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

One 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

WP to LinkedIn Auto Publish Powered By : XYZScripts.com