Spring Java Config DI defining and a ("concrete interface") of JpaRepository - spring-boot

I have the below code.
Note that I have an interface MySuperCoolEntityRepositoryContract.
And I have a "concrete interface" MySuperCoolEntityJpaRepository that implements my above MySuperCoolEntityRepositoryContract interface and JpaRepository.
All of that works fine with #ComponentScan.
I am changing my code to "java config", aka a centralized location where I can code up my DI definitions. (Also known as CompositionRoot in some circles).
The issue is when I try to "code up" the concrete for the interface. (Skip down to later in this question.
package com.me.domain.jpaentities;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
#Entity(name = "MySuperCoolEntityTableName")
public class MySuperCoolEntity implements Serializable {
#Id
#Column(name = "CoolSurrogateKeyColumn")
private String coolSurrogateKey;
#Column(name = "CoolMagicValueColumn")
private String coolMagicValue;
public String getCoolSurrogateKey() {
return this.coolSurrogateKey;
}
public void setCoolSurrogateKey(String coolSurrogateKey) {
this.coolSurrogateKey = coolSurrogateKey;
}
public String getCoolMagicValue() {
return this.coolMagicValue;
}
public void setCoolMagicValue(String coolMagicValue) {
this.coolMagicValue = coolMagicValue;
}
}
===============
package com.me.dal.repositories.interfaces;
import com.me.domain.jpaentities.MySuperCoolEntity;
import java.util.Collection;
public interface MySuperCoolEntityRepositoryContract {
Collection<MySuperCoolEntity> findByCoolMagicValue(String coolMagicValue);
}
=========================
package com.me.dal.repositories;
import com.me.dal.repositories.interfaces.MySuperCoolEntityRepositoryContract;
import com.me.domain.jpaentities.MySuperCoolEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
#Repository
public interface MySuperCoolEntityJpaRepository extends MySuperCoolEntityRepositoryContract, JpaRepository<MySuperCoolEntity,String> {
Collection<MySuperCoolEntity> findByCoolMagicValue(String coolMagicValue);
}
Now this issue.
package com.me.myapplication.configuration;
import com.me.dal.repositories.MySuperCoolEntityJpaRepository;
import com.me.dal.repositories.interfaces.MySuperCoolEntityRepositoryContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MyCompositionRoot {
#Bean
public MySuperCoolEntityRepositoryContract getAMySuperCoolEntityRepositoryContract()
{
return new MySuperCoolEntityJpaRepository(); /* << issue is here, this is an abstract class, aka, an interface with some methods defined */
}
}
Using the super cool JpaRepository "concrete interface" aka "really an abstract class but called an interface" aka "Interface Default Methods" ( see https://dzone.com/articles/interface-default-methods-java ) ........
The exact error is:
MySuperCoolEntityJpaRepository is abstract; cannot be instantiated
I do understand the error. MySuperCoolEntityJpaRepository is abstract. I get that.
But with this super cool "just extend JpaRepository and get all kinds of default functionality".....
How do I register a concrete JpaRepository with Spring DI (specifically with "code it up" java config ?
............
I tried making it a "class".
public class MySuperCoolEntityJpaRepository extends MySuperCoolEntityRepositoryContract, JpaRepository<MySuperCoolEntity,String>
but that wants me to define all those built in methods like "findAll",etc, etc.

Spring boot magically provides implementation for the methods defined in your interface. The #EnableJpaRepositories scans all packages below the package for interfaces extending JpaRepository and creates a Spring bean for it that is backed by an implementation of SimpleJpaRepository (spring data provides default imlpementations of CRUD repository through this class).
Your interface MySuperCoolEntityJpaRepository extends the interface MySuperCoolEntityRepositoryContract , but you only extend the JpaRepository on the interface MySuperCoolEntityJpaRepository which means spring will only provide the default implementations for methods in the interface MySuperCoolEntityJpaRepository . So try it like :
public interface MySuperCoolEntityRepositoryContract extends JpaRepository<MySuperCoolEntity,String>{
Collection<MySuperCoolEntity> findByCoolMagicValue(String coolMagicValue);
}
then extend this in your repository like :
#Repository
public interface MySuperCoolEntityJpaRepository extends MySuperCoolEntityRepositoryContract {
Collection<MySuperCoolEntity> findByCoolMagicValue(String coolMagicValue);
}
Related Post : how annotation #Repository in java spring work?

I figured out a workaround. I don't really like it, but I guess it works.
I also added MySuperCoolEntityBalServiceContract (you can get the idea from just the below), so you know why/how I need to have the getAMySuperCoolEntityRepositoryContract method in my CompositionRoot class below.
I'll leave this (not marked) as the answer in case someone else has a better way, or sees issue(s) with the below. I don't like the EntitiyManager work around, but it got things moving.
package com.me.myapplication.configuration;
import com.me.apicore.managers.MySuperCoolEntityBalService;
import com.me.apicore.managers.interfaces.MySuperCoolEntityBalServiceContract;
import com.me.dal.repositories.interfaces.MySuperCoolEntityRepositoryContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.inject.Inject;
import javax.persistence.EntityManager;
#Configuration
public class MyCompositionRoot {
#Inject
private EntityManager entManager; /* part of the work around trick */
#Bean
public MySuperCoolEntityBalServiceContract getAMySuperCoolEntityBalServiceContract() {
return new MySuperCoolEntityBalService(this.getAMySuperCoolEntityRepositoryContract());
}
#Bean
public MySuperCoolEntityRepositoryContract getAMySuperCoolEntityRepositoryContract() {
//return new MySuperCoolEntityJpaRepository(); /* does not work. :( */
RepositoryFactorySupport factory = new JpaRepositoryFactory(entManager);
MySuperCoolEntityRepositoryContract repository = factory.getRepository(MySuperCoolEntityRepositoryContract.class);
return repository;
}
}
And I tweaked this (note the addition of the RepositoryDefinition annotation)
package com.me.dal.repositories.interfaces;
import com.me.domain.jpaentities.MySuperCoolEntity;
import org.springframework.data.repository.RepositoryDefinition;
import java.util.Collection;
#RepositoryDefinition(domainClass = MySuperCoolEntity.class, idClass = String.class)
public interface MySuperCoolEntityRepositoryContract {
Collection<MySuperCoolEntity> findByCoolMagicValue(String coolMagicValue);
}

Related

In Spring boot application is #Component annotation optional for repo

I have created the basic application using Spring boot using JPA. I have added #AutoWired annotation for RatingRepo in RatingResource, but haven't added #Component annotation to RatingRepo
package com.example.demo;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.RatingsRateService.model.Rating;
import com.example.demo.RatingsRateService.model.UserRating;
#RestController
#RequestMapping("ratingsdata")
public class RatingResource {
#Autowired
RatingRepo repo;
/*
* #RequestMapping("/{movieId}") public Rating
* getRating(#PathVariable("movieId") String movieId) { return new
* Rating(movieId,7); }
*/
#RequestMapping("users/{userid}")
public UserRating getRating(#PathVariable("userid") int userid) {
List<Rating> ratings =repo.findByUserId(userid);
/*
* List<Rating> ratings = Arrays.asList(new Rating("1",4), new Rating("2",3),
* new Rating("3",2));
*/
System.out.println(ratings);
UserRating userRating = new UserRating();
userRating.setUserRating(ratings);
return userRating;
}
}
package com.example.demo;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.example.demo.RatingsRateService.model.Rating;
//to update the data in database , created the interd=face and will implement
//class,primary key
public interface RatingRepo extends JpaRepository<Rating, Integer>{
#Query(" from Rating where userid = ?1")
List<Rating> findByUserId( int userid);
}
. Still, it is working fine. Can you someone please explain why it is so? Or it is not needed to add #Component annotation for the repo?
first of there is #Repository annotation required not #Component
and #Repository also auto configure due to below:
Probably you are using spring boot
Spring Data repositories usually extend from the Repository or CrudRepository interfaces. If you are using auto-configuration, repositories will be searched from the package containing your main configuration class (the one annotated with #EnableAutoConfiguration or #SpringBootApplication) down.
ref: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-spring-data-jpa-repositories

How to resolve javax.enterprise.inject.IllegalProductException when using JPA repository

I am getting exception when calling findAll method on CrudRepository in quarkus - based code.
Here is my database configuration.
quarkus.datasource.url = jdbc:postgresql://localhost:5432/postgres
quarkus.datasource.driver = org.postgresql.Driver
quarkus.datasource.username = ZZZZ
quarkus.datasource.password = XXX
Here is repository code.
import org.springframework.data.repository.CrudRepository;
public interface FruitRepository extends CrudRepository<FruitEntity, Integer> {
}
Here is client code.
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/fruitz")
public class FruitController {
#Inject
FruitRepository fruitRepository;
#GET
#Produces("application/json")
public Iterable<FruitEntity> findAll() {
return fruitRepository.findAll();
}
}
I am getting following exception.
Caused by: javax.enterprise.inject.IllegalProductException: Normal scoped producer method may not return null: io.quarkus.agroal.runtime.DataSourceProducer.createDefaultDataSource()
at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_Bean.create(DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_Bean.zig:306)
at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_Bean.create(DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_Bean.zig:244)
at io.quarkus.arc.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:69)
at io.quarkus.arc.ComputingCache$CacheFunction.lambda$apply$0(ComputingCache.java:99)
at io.quarkus.arc.LazyValue.get(LazyValue.java:26)
at io.quarkus.arc.ComputingCache.getValue(ComputingCache.java:41)
at io.quarkus.arc.AbstractSharedContext.get(AbstractSharedContext.java:20)
at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_ClientProxy.arc$delegate(DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_ClientProxy.zig:152)
at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_ClientProxy.getConnection(DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_ClientProxy.zig:23)
You have to annotate your interface with #Named to make it available for injection. Also ensure to have quarkus-jdbc-postgresql added as a dependency.

Unable to inject dependency in Junit test

Having some trouble injecting a dependency in one of my JUnit test classes.
I believe the TestApplication is not package scanning or is not being loaded.
Code below:
package com.mitto.repositories;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.mitto.MittoApplicationTests;
import com.mitto.domain.User;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration( classes= { MittoApplicationTests.class } )
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
#DatabaseSetup("UserRepositoryTest.xml")
public class UserRepositoryTest {
#Autowired
UserRepository repository;
private static final long FACEBOOK_ID = 1234567;
#Test
public void getUserById() {
User user = repository.findOne(1L);
assertNotNull(user);
assertEquals( user.getFacebookId(), FACEBOOK_ID );
}
}
MittoApplicationTests.java
package com.mitto;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class MittoApplicationTests {
#Test
public void contextLoads() {
}
}
UserRepository.java
package com.mitto.repositories;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.mitto.domain.User;
#Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
User findByFacebookId( long facebookId );
User findByAuthToken( String token );
}
I can't see anything wrong with this.
Sometimes, a working example is better than fixes.
Here is a working example:
First, in your configuration class
#SpringBootApplication
#ComponentScan(value = "com.mitto")
#EnableJpaRepositories(value = "com.mitto")
#EntityScan(basePackages = {"com.mitto.domain"}, basePackageClasses = {Jsr310JpaConverters.class})
public class MittoApplicationTests {
}
Second, in your test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MittoApplicationTests.class) // replace the #ContextConfiguration with #SpringBootTest
// rest of of your annotations ...
public class UserRepositoryTest {
#Autowired
UserRepository repository;
// your test cases
}
A Spring Boot application is just a Spring ApplicationContext, so nothing very special has to be done to test it beyond what you would normally do with a vanilla Spring context. One thing to watch out for though is that the external properties, logging and other features of Spring Boot are only installed in the context by default if you use SpringApplication to create it.
Spring Boot provides a #SpringBootTest annotation which can be used as an alternative to the standard spring-test #ContextConfiguration annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext used in your tests via SpringApplication.
Please read the documentation for more details:
SpringBootTest annotation
boot-features-testing

what is the idiomatic way to use ConfigurationProperties and EnableConfigurationProperties in tests?

i am trying to setup unit tests for some elements to be used within a spring(-boot) application, and i struggled with setup around ConfigurationProperties and EnableConfigurationProperties. the way i finally got it to work doesn't seem consistent with the examples that i have seen in that i have witnessed needing both ConfigurationProperties and EnableConfigurationProperties on my configuration class, which doesn't seem right, and i was hoping that someone might provide some guidance.
here is a simplified example:
JavaTestConfiguration.java
package com.kerz;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.constraints.NotNull;
#Configuration
#ConfigurationProperties
#EnableConfigurationProperties
public class JavaTestConfiguration {
public void setFoo(String foo) {
this.foo = foo;
}
#NotNull
String foo;
#Bean
String foo() {
return foo;
}
}
JavaTestConfigurationTest.java
package com.kerz;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {JavaTestConfiguration.class})
#TestPropertySource("classpath:test.properties")
public class JavaTestConfigurationTest {
#Autowired
String foo;
#Test
public void shouldWork() throws Exception {
assertEquals("foo", "bar", foo);
}
}
test.properties
foo=bar
Your test is more integration test if you are starting Spring context. Therefore you should test also production spring configuration.
I would advise not to create testing configuration. Use one production configuration for testing.
You are also using #TestPropertySource annotation, which is used when you need to define test specific properties. If you can test with PROD configuration do not use it.

Spring set bean name with #Named

I use javax standard annotation #Named for defining beans in spring4. To set the bean name I could tried #Named("MyBean") but it did not change the bean name.
I used spring Component annotation #Component("MyBean") and it worked fine.
Is it possible to set the bean name by using #Named
The bean is defined asL
#Named("myBean") //This not
#Component("myBean") //This works
#Scope("session")
public class User implements HttpSessionBindingListener, Serializable {
The application.context is
<context:component-scan base-package="foo.bar" />
I agree to what #fabian has said. You can use #Named annotation to set the bean name. If bean name doesn't matches, it falls back to auto-wiring by type.
I tried couple of examples. They worked for me.
AppConfig.java
package com.named;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan
public class AppConfig {
}
NamedService.java
package com.named;
import javax.inject.Named;
#Named("namedTestDependency")
public class NamedService {
public void namedMethod(){
System.out.println("Named method");
}
}
NamedServiceTest.java
package com.named;
import static org.junit.Assert.assertNotNull;
import com.named.AppConfig;
import com.named.NamedService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
public class NamedServiceTest {
//Matched by name of dependency
#Autowired
private NamedService namedTestDependency;
//Falls back to auto-wiring by type
#Autowired
private NamedService noDeclaration;
#Test
public void testAutowiring(){
assertNotNull(namedTestDependency);
assertNotNull(noDeclaration);
}
}

Resources