Issue
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}
Solution
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.
Answered By - R.G
Answer Checked By - Marilyn (JavaFixing Volunteer)