0

How do I wire a (subclass/subinterface) of JpaRepository via Xml configuration?

So I have an "implementation" of JpaRepository

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

import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.Optional;

public interface MyDepartmentJpaRepo extends JpaRepository<Department, Long> {

   /* "lookup strategy".  see https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods */
    Optional<Department> findDepartmentByDepartmentNameEquals(String departmentName);

    Collection<Department> findByCreateOffsetDateTimeBefore(OffsetDateTime zdt);

}

and the entity

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.OffsetDateTime;

@Entity
@Table(name = "DepartmentTable")
public class Department {

    @Id
    @Column(name = "DepartmentKey", unique = true)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long departmentKey;

    @Column(name = "DepartmentName", unique = true)
    private String departmentName;

    @Column(name = "CreateOffsetDateTime", columnDefinition = "TIMESTAMP WITH TIME ZONE" )
    private OffsetDateTime createOffsetDateTime;

    public long getDepartmentKey() {
        return departmentKey;
    }

    public void setDepartmentKey(long departmentKey) {
        this.departmentKey = departmentKey;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    public OffsetDateTime getCreateOffsetDateTime() {
        return createOffsetDateTime;
    }

    public void setCreateOffsetDateTime(OffsetDateTime createOffsetDateTime) {
        this.createOffsetDateTime = createOffsetDateTime;
    }
}

and a class where I need to inject the MyDepartmentJpaRepo

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

public class DepartmentManager implements IDepartmentManager {

    private final Logger logger;
    private final MyDepartmentJpaRepo deptRepo;

    /* The Inject annotation marks which constructor to use for IoC when there are multiple constructors */
    @Inject
    public DepartmentManager(MyDepartmentJpaRepo deptRepo) {
        this(LoggerFactory.getLogger(DepartmentManager.class), deptRepo);
    }

    public DepartmentManager(Logger lgr, MyDepartmentJpaRepo deptRepo) {
        if (null == lgr) {
            throw new IllegalArgumentException("Logger is null");
        }

        if (null == deptRepo) {
            throw new IllegalArgumentException("IDepartmentDomainData is null");
        }

        this.logger = lgr;
        this.deptRepo = deptRepo;
    }

    @Override
    public Collection<Department> getAll() {
        List<Department> returnItems = this.deptRepo.findAll();
        return returnItems;
    }

    @Override
    public Optional<Department> getSingle(long key) {
        Optional<Department> returnItem = this.deptRepo.findById(key);
        return  returnItem;
    }

    @Override
    public Optional<Department> getSingleByName(String deptName) {
        Optional<Department> returnItem = this.deptRepo.findDepartmentByDepartmentNameEquals(deptName);
        return  returnItem;
    }

    public Collection<Department> getDepartmentsOlderThanDate(OffsetDateTime zdt)
    {
        Collection<Department> returnItems = this.deptRepo.findByCreateOffsetDateTimeBefore(zdt);
        return returnItems;
    }

    @Override
    public Department save(Department item) {
        Department returnItem = this.deptRepo.save(item);
        return  returnItem;
    }
}

and the interface of the "manager" for completeness.

import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.Optional;

public interface IDepartmentManager {

    Collection<Department> getAll();

    Optional<Department> getSingle(long key);

    Optional<Department> getSingleByName(String deptName);

    Department save(Department item);

    Collection<Department> getDepartmentsOlderThanDate(OffsetDateTime zdt);
}

The issue is in the applicationcontext.xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="jpaSetup.di.xml"/>

    <bean id="MyDepartmentJpaRepoBean" class="com.mycompany.blah.blah.blah.MyDepartmentJpaRepo">
    </bean>

    <bean id="IDepartmentManagerBean" class="com.mycompany.blah.blah.blah.DepartmentManager">
        <constructor-arg ref="MyDepartmentJpaRepoBean"/>
    </bean>



</beans>

So..the spring-boot-data makes one define the (sub-interfaced JpaRepository) AS AN INTERFACE

interface MyDepartmentJpaRepo extends JpaRepository<Department, Long>

So when you try to xml-define the IoC/DI..you get "interface not allowed for non-abtract beans".

This looks like a catch-22 ...... :(

The magic question:

How does one use xml-config for IoC/DI...................and take advantage of a sub-interfaced JpaRepository ????

APPEND:

If I add in "jpa:repositories", then I don't have a constructor-arg for the "manager".

http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<import resource="jpaSetup.di.xml"/>

<jpa:repositories base-package="com.mycompany.blah.blah.blah" />

--> -->

<bean id="IDepartmentManagerBean" class="com.mycompany.organizationdemo.businesslayer.managers.DepartmentManager">
    <constructor-arg ref="NotDoesNotExistMyDepartmentJpaRepoBean"/> <!-- DOES NOT WORK -->
</bean>

..................

other files below for completeness.

jpaSetup.di.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <beans>

        <bean id="myLocalContainerEntityManagerFactoryBeanBean"
              class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="packagesToScan" value="com.blah.blah.blah.entities"/>
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                    <property name="showSql" value="${spring.jpa.show-sql}"/>
                    <property name="generateDdl" value="${spring.jpa.generate-ddl}"/>
                </bean>
            </property>
            <!-- See https://stackoverflow.com/questions/16088112/how-to-auto-detect-entities-in-jpa-2-0/16088340#16088340 -->
            <property name="jpaProperties">
                <props>
                    <prop key="hibernate.hbm2ddl.auto">${spring.jpa.hibernate.ddl-auto}</prop>
                    <prop key="hibernate.dialect">${spring.jpa.properties.hibernate.dialect}</prop>
                </props>
            </property>
        </bean>

        <bean id="dataSource"
              class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="url" value="${SPRING_DATASOURCE_URL}"/>
            <property name="username" value="${SPRING_DATASOURCE_USERNAME}"/>
            <property name="password" value="${SPRING_DATASOURCE_PASSWORD}"/>
            <property name="driverClassName" value="${SPRING_DATASOURCE_DRIVER-CLASS-NAME}"/>
        </bean>


        <bean id="transactionManager"
              class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="myLocalContainerEntityManagerFactoryBeanBean"/>
        </bean>

        <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

        <bean id="persistenceExceptionTranslationPostProcessor" class=
                "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

    </beans>

</beans>

and application.yml

spring:
  jpa:
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: update
      naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
  datasource:
    url: ${SPRING_DATASOURCE_URL}
    username: ${SPRING_DATASOURCE_USERNAME}
    password: ${SPRING_DATASOURCE_PASSWORD}
    driverClassName: ${SPRING_DATASOURCE_DRIVER-CLASS-NAME}
granadaCoder
  • 26,328
  • 10
  • 113
  • 146

2 Answers2

1

Based on the reference documenation : XML Configuration

Following configuration works based on the understanding that

Each bean is registered under a bean name that is derived from the interface name, so an interface of UserRepository would be registered under userRepository.

So the bean name for interface

public interface MyDepartmentJpaRepo extends JpaRepository<Department, Long> {
..
}

would be as follows : myDepartmentJpaRepo

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:jpa="http://www.springframework.org/schema/data/jpa"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
     https://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/data/jpa
     https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

   <jpa:repositories base-package="com.mycompany.organizationdemo.businesslayer.repository"/>

    <bean id="IDepartmentManagerBean" class="com.mycompany.organizationdemo.businesslayer.managers.DepartmentManager">
        <constructor-arg ref="myDepartmentJpaRepo"/>
    </bean>
</beans>

For verification , I autowired and used the DepartmentManager instance as follows

@Autowired
IDepartmentManager manager;

Spring boot version : 2.2.6

Also the documentation mentions the following

One way to do so is by using the Spring namespace that is shipped with each Spring Data module that supports the repository mechanism, although we generally recommend using Java configuration.

Note: the package names are modified to match the question , please modify as required.

Hope this helps.

R.G
  • 6,436
  • 3
  • 19
  • 28
0

The other answer is far more simple and better than this answer. (Answer from "R.G."). However, I did find a ugly workaround for future readers.

DO NOT PREFER THIS ANSWER OVER the one from R.G. !

import org.springframework.stereotype.Component;

import javax.inject.Inject;

@Component
public class DepartmentJpaRepositoryFactory {

    @Inject
    private MyDepartmentJpaRepo autoinjectedDepartmentJpaRepositoryWorkaround;

    /* this is a workaround for using explicit xml IOC with spring-data.
    * because it is "interface MyDepartmentJpaRepo" (not a class), you cannot do traditional xml IoC definitions :(
    * this is a workaround.  this factory should NEVER be used by the code base, only by spring-di  */
    public MyDepartmentJpaRepo getInstanceDepartmentJpaRepository() {
        return this.autoinjectedDepartmentJpaRepositoryWorkaround;
    }
}

and then applicationcontext.xml

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

<bean id="DepartmentManagerBean" class="com.blah.blah.blah.managers.DepartmentManager">
    <constructor-arg ref="DepartmentJpaRepositoryViaFactoryMethodBean"/>
</bean>

<bean id="DepartmentJpaRepositoryServiceLocatorBean" class="com.blah.blah.blah.jpa.factories.DepartmentJpaRepositoryFactory">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="DepartmentJpaRepositoryViaFactoryMethodBean"
      factory-bean="DepartmentJpaRepositoryServiceLocatorBean"
      factory-method="getInstanceDepartmentJpaRepository"/>

the above is an ugly and nasty workaround using

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-class-instance-factory-method

"Instantiation by Using an Instance Factory Method"

granadaCoder
  • 26,328
  • 10
  • 113
  • 146