A module can not access JpaRepository interface that is in the other module - spring-data-jpa

I am using IntelliJ and Gradle for a sample project. There are two modules.
demo-core module
It has entity and repository classes. build.gradle file is like the below.
apply plugin: 'java'
group 'com.example'
version '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'com.h2database:h2:1.4.200'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.3'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
test {
useJUnitPlatform()
}
I added CustomerRepository class for Customer entity in demo-core module.
package example.springboot.entity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
demo-web module
This is a web module and uses the repository interface like the below.
#SpringBootApplication
public class DemoWebApp {
public static void main(String[] args) {
SpringApplication.run(DemoWebApp.class, args);
}
#Bean
public CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {
// save a few customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
};
}
}
This is build.gradle file for demo-web module.
plugins {
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group 'com.example'
version '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation project(':demo-core')
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
test {
useJUnitPlatform()
}
but I can't use JpaRepsitory methods with this error.
/Users/dgkim/Downloads/demo/demo-web/src/main/java/example/springboot/DemoWebApp.java:19: error: cannot access JpaRepository
repository.save(new Customer("Jack", "Bauer"));
^
class file for org.springframework.data.jpa.repository.JpaRepository not found
So, I created a new interface wrapping the CustomerRepository in demo-core module like this.
#Service
public class CustomerFinder {
#Autowired
private CustomerRepository customerRepository;
public Optional<Customer> findCustomer(Long id) {
return customerRepository.findById(id);
}
}
My Controller class uses the wrapper interface like the below.
#RestController
public class CustomerController {
#Autowired
private CustomerFinder finder;
#GetMapping("/customer/{id}")
public String customer(#PathVariable Long id) {
Optional<Customer> customerOptional = finder.findCustomer(id);
if(customerOptional.isPresent()) return "find customer. " + customerOptional.get().getLastName();
else return "no entity";
}
}
It works. JpaRepository methods can be accessed in the same module but demo-web module that has a dependency on demo-core can not access it. DemoWebApp class can access CustomerRepository interface itself but can not access the super interface (JpaRepository).
How can I resolve this issue?

try to change core module dependencies from implementation to api
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.3'
to
api 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.3'
You can see the difference between api and implementation here

Related

Micronauts Failed to inject value for parameter [entityManager] of class: UserRepository

I'm trying to inject EntityManager in the repository.
compilation was successful but when i run the application and send a post request i got this error:
Unexpected error occurred: Failed to inject value for parameter [entityManager] of class: aututor.apps.DAO.Repository.RepositoryImp.UserRepository
Message: No bean of type [javax.persistence.EntityManager] exists. Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
Path Taken: new UserController([UserService service]) --> new UserServiceImpl([IUserRepository userRepository]) --> new UserRepository([EntityManager entityManager])
io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [entityManager] of class: aututor.apps.DAO.Repository.RepositoryImp.UserRepository
this is the repository :
package aututor.apps.DAO.Repository.IRepository;
import aututor.apps.DAO.Model.*;
public interface IUserRepository {
public User save(User User);
public User findByEmail(String Email);
}
package aututor.apps.DAO.Repository.RepositoryImp;
import aututor.apps.DAO.Model.User;
import aututor.apps.DAO.Repository.IRepository.IUserRepository;
import io.micronaut.configuration.hibernate.jpa.scope.CurrentSession;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#Singleton
public class UserRepository implements IUserRepository {
#PersistenceContext
protected EntityManager entityManager;
public UserRepository(#CurrentSession EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public User save(User User) {
return entityManager.merge(User);
}
#Override
public User findByEmail(String Email) {
return entityManager.createQuery("SELECT user FROM User user WHERE user.Email = :email", User.class)
.setParameter("email", Email)
.getSingleResult();
}
}
this is the service :
package aututor.apps.Services.ServicesImpl;
import aututor.apps.DAO.Model.User;
import aututor.apps.DAO.Repository.IRepository.IUserRepository;
import aututor.apps.DTO.Mapper.UserMapper;
import aututor.apps.DTO.UserDTO;
import aututor.apps.Services.IServices.UserService;
import aututor.apps.Util.Response;
import javax.inject.Singleton;
#Singleton
public class UserServiceImpl implements UserService {
protected final IUserRepository userRepository;
protected final UserMapper userMapper= new UserMapper();
public UserServiceImpl(IUserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public Response CreateUser(UserDTO User) {
if(userRepository.findByEmail(User.getEmail())==null) {
User u = userRepository.save(userMapper.ToUser(User));
return new Response("request has succeeded",userMapper.ToUserDTO(u),200);
}
else {
return new Response("Not Acceptable : Adress email already exist",null,406);
}
}
#Override
public Response UpdateUser(UserDTO User) {
return null;
}
#Override
public Response DeleteUser(UserDTO User) {
return null;
}
#Override
public Response FindUserByID(Long Id) {
return null;
}
}
Controller :
package aututor.apps.Controllers;
import aututor.apps.DTO.UserDTO;
import aututor.apps.Services.IServices.UserService;
import aututor.apps.Util.Response;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
#Controller("/rest/User")
public class UserController {
protected final UserService service ;
public UserController(UserService service) {
this.service = service;
}
#Post("/")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response CreateUser(#Body UserDTO user) {
return service.CreateUser(user);
}
}
build.gradle :
dependencies {
annotationProcessor platform("io.micronaut:micronaut-bom:$micronautVersion")
annotationProcessor "io.micronaut:micronaut-inject-java"
annotationProcessor "io.micronaut:micronaut-validation"
annotationProcessor "io.micronaut.data:micronaut-data-processor:1.0.0"
implementation platform("io.micronaut:micronaut-bom:$micronautVersion")
implementation "io.micronaut:micronaut-security-jwt"
implementation "io.micronaut:micronaut-http-client"
implementation "io.micronaut:micronaut-validation"
implementation "io.micronaut:micronaut-http-server-netty"
implementation "io.micronaut.configuration:micronaut-hibernate-jpa"
implementation("io.micronaut:micronaut-spring")
compile "io.micronaut.configuration:micronaut-jdbc-tomcat"
compile "io.micronaut:micronaut-runtime"
compile "io.micronaut:micronaut-inject"
compile("io.micronaut.data:micronaut-data-hibernate-jpa")
compile("io.micronaut.data:micronaut-data-jdbc")
compile("org.postgresql:postgresql:42.2.1")
runtimeOnly "ch.qos.logback:logback-classic:1.2.3"
testAnnotationProcessor platform("io.micronaut:micronaut-bom:$micronautVersion")
testAnnotationProcessor "io.micronaut:micronaut-inject-java"
testImplementation platform("io.micronaut:micronaut-bom:$micronautVersion")
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "io.micronaut.test:micronaut-test-junit5"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
}
application.yml:
---
micronaut:
application:
name: apps
server:
port: 8089
---
datasources:
default:
url: jdbc:postgresql://localhost:5432/db
username: root
password: ''
schema-generate: CREATE_DROP
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.PostgreSQL95Dialect
entity-scan:
packages: 'apps.model'
i have tried to work with CrudRepository and there was a problem there also. Everything looks correct i don't know if i miss something.
I started working with Micronaut JPA and Hibernate and came across the same kind of issue that you have. Whatever scans the application.yml is not good at all in telling you if you didn't define your jpa settings properly. After getting experience from beating my head over for days trying to get Micronaut to work with data (and on the brink of giving up on using Micronaut from accessing data at all) from looking at your setup, my best bet is you defined your entity-scan packages wrong. If you specify an entity scan package it must be a package that encapsulates or is a parent encapsulating package of your classes that are annotated with #Entity. In your case you specified apps.model as your package, but I see no where in the code you provided where you are referencing anything that follows that package format. Not sure why you have a aututor.apps.DAO.Model.User package. Model should be separate from your DAO. The easiest thing would be to just delete entity-scan packages and let all your classes get scanned. Specifying the package is supposed to improve performance, but if you don't have a lot of files I don't think it really makes a difference. You could have specified aututor or aututor.apps or aututor.apps.DAO or aututor.apps.DAO.Model as your scan package and that should work if your classes with #Entity are in aututor.apps.DAO.Model package.
apply annotation #Repository on your repository class.

Gradle standalone plugin unknown property when loaded from github repository

I've created a simple standalone Gradle plugin in Java and a Maven repository on GitHub for publishing the plugin. Everything works fine except retrieving project properties that should be available with the plugin. When I apply the plugin in another Gradle project, it can't find those properties.
It happens only when the plugin is loaded from the remote repository on GitHub. When loaded from a local repository, it works. Can the plugin implementation be somehow set to provide properties even from a remote repository? Or is this some GitHub feature that can't be hacked?
Details follow:
My build.gradle in the another project looks something like this:
buildscript {
repositories {
maven {
url "GITHUB_REPO_URL"
}
}
dependencies {
classpath "custom_plugin_group:custom_plugin:1.0"
}
}
apply plugin: "custom_plugin"
task propertyTest {
doLast {
println project.customProperty
}
}
When I try to run the the task propertyTest, it fails complaining that "Could not get unknown property 'customProperty' for root project 'another_project' of type org.gradle.api.Project."
I'm creating the property in the method apply in the main plugin class. I have tried following three approaches:
// First approach - adding a simple value to extensions
public class CustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getExtensions().add("customProperty", "Custom property value");
}
}
// Second approach - setting extra property to extensions
public class CustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getExtensions().getExtraProperties().set("customProperty", "Custom property value");
}
}
// Third approach - adding a property instance to extensions
public class CustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
Property<String> customProperty = project.getObjects().property(String.class);
customProperty.set("Custom property value");
project.getExtensions().add("customProperty ", customProperty);
}
}
For creating extensions for your Gradle plugin you need create POJO class with fields:
class YourExtension {
String customProperty
}
And then create extension in your plugin class:
public class CustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getExtensions().create("extensionName", YourExtension.class);
}
}
Now you can use extension in build.gradle file:
extensionName {
customProperty = "value"
}

Spring Boot/Jersey Component Scan Issue

I'm using Spring Boot and Jersey for a small app, and I need to add #ComponentScan to scan a third-party library for some services. The problem is, whenever I add the #ComponentScan annotation, it seems that the Spring MVC DispatcherServlet takes over, and I can no longer hit my Jersey endpoints (it doesn't look like the JerseyServlet is even loaded when I add the annotation). How do I get component scan to work alongside my Jersey endpoints?
build.gradle
buildscript {
ext {
springBootVersion = '1.5.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'demo'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-jersey')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Application
#SpringBootApplication
#ComponentScan(basePackages = {"com.abc"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Config
#Component
#Configuration
public class JerseyConfig extends ResourceConfig {
#PostConstruct
public void init() {
packages("com.demo.rest.endpoint");
register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), Level.INFO, LoggingFeature.DEFAULT_VERBOSITY, null));
}
}
Endpoint
#Component
#Path("/test")
public class TestEndpoint {
#GET
public String test() {
return "Testing Jersey Endpoint";
}
}
It's a problem on how jersey scans classes on fatjars. Keep everything and add register each class on your JerseyConfig.
#Component
#Configuration
public class JerseyConfig extends ResourceConfig
{
#PostConstruct
public void init() {
[...]
register(TestEndpoint.class);
}

Add customer behaviour to all spring data Jpa repositories in CDI context

Am successfully injecting jpa repositories using CDI. I wanted to add custom behaviour(soft deletes) to all repositories. When using spring I can enable customer behaviour by specifying the repository base class
#EnableJpaRepositories(repositoryBaseClass = StagedRepositoryImpl.class)
How do I specify the same in CDI? Thanks in advance.
To add custom behaviour to Jpa Repositories(in your case for delete),
1. Create a base repository like below:
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
#Override
default void delete(T entity){
// your implementation
}
}
2. Now inherit Jpa Repositories from custom repository(i.e BaseRepository) like below:
public interface EmployeeRepository extends BaseRepository<Employee, Long> {
}
3. Inject your repository into Service class and call the delete method.
#Service
class EmployeeService {
#Inject
private EmployeeRepository employeeRepository;
public void delete(Long id) {
employeeRepository.delete(id);
}
}
Now whenever you call delete on repositories which are child of BaseRepository, your custom implementation for delete will be invoked.
Here is the way to add custom logic to your repositories:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
Basically you create a custom repository named {YourRepositoryName}Custom
interface UserRepositoryCustom {
public void someCustomMethod(User user);
}
And implement it:
class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
Your main repository should extend the custom one.
Hope this helps!

How can I override "ribbonServerListFilter"

I want to be able to override the default Spring Cloud 'ribbonServerListFilter' implementation. I have read the docs but I always get an error regarding a bean it cannot find (IClientConfig).
So I have a simple class like:
#Configuration
public class FooConfiguration {
#Bean
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
MyListFilter filter = new MyListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
}
and:
#Configuration
#RibbonClient(name = "foo", configuration = FooConfiguration.class)
public class TestConfiguration {
}
But then at runtime I get:
org.springframework.beans.factory.UnsatisfiedDependencyException: \
Error creating bean with name 'ribbonServerListFilter' defined in class path \
resource [FooConfiguration.class]: Unsatisfied dependency expressed through \
constructor argument with index 0 of type \
[com.netflix.client.config.IClientConfig]: : No qualifying bean of type \
[com.netflix.client.config.IClientConfig] found for dependency
So what am I doing wrong?
This is with Spring Boot 1.3.1.RELEASE.
Thanks in advance.
Henry
UPDATE:
Adding full source after Dave's comments.
package com.domain1;
...
#Configuration
public class FooConfiguration {
#Bean
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
System.out.println("My ribbonServerListFilter will be used");
MyListFilter filter = new MyListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
}
package com.domain1;
...
public class MyListFilter extends ZonePreferenceServerListFilter {
#Override
public List<Server> getFilteredListOfServers(List<Server> servers) {
System.out.println("This is my version");
return super.getFilteredListOfServers(servers);
}
}
And the main application in a different package:
package com.domain2;
...
#Configuration
#RibbonClient(name = "foo", configuration = FooConfiguration.class)
public class TestConfiguration {
}
package com.domain2;
...
#Component
#Configuration
public class Runner implements CommandLineRunner {
#Autowired
private DiscoveryClient discoveryClient;
#Autowired
private LoadBalancerClient loadBalancer;
public void run(String... strings) throws Exception {
System.out.println("all");
for (ServiceInstance s : discoveryClient.getInstances("service")) {
System.out.println(s.getHost() + ":" + s.getPort());
}
System.out.println("from lb");
ServiceInstance instance = loadBalancer.choose("service");
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
package com.domain2;
...
#EnableDiscoveryClient
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
So in the second test when calling 'loadBalancer.choose' I expected my code to be called.
You have to make sure your FooConfiguration is not component scanned. Put it in a separate package not under the main application, or don't use #ComponentScan.