spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
2.99k stars 1.41k forks source link

Cannot resolve reference to bean 'jpaSharedEM_entityManager' while setting bean property 'entityManager' #3190

Closed KhanamDEV closed 11 months ago

KhanamDEV commented 12 months ago

Hi team, I have some issue when using AbstractRoutingDataSource for custom datasource: This is my error: Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtAuthenticationFilter' defined in file [JwtAuthenticationFilter.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'authenticationService' defined in file [AuthenticationService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'userRepositoryInterface' defined in com.tkg.MasterSystem.repositories.UserRepositoryInterface defined in @EnableJpaRepositories declared on DatasourceRoutingConfiguration: Cannot resolve reference to bean 'jpaSharedEM_entityManager' while setting bean property 'entityManager'

This is my config:

@Service
@RequiredArgsConstructor
public class AuthenticationService implements AuthenticationServiceInterface {

    @Autowired
    Environment environment;

    @Value("${JWT_SECRET_KEY}")
    private String jwtSecret;

    private final UserRepositoryInterface userRepository;
public class ClientDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return ClientDatabaseContextHolder.getClientDatabase();
    }

    public void initDataSource(DataSource companyA, DataSource companyB){
        Map<Object, Object> datasourceMap = new HashMap<>();
        datasourceMap.put(ClientDatabase.COMPANY_A, companyA);
        datasourceMap.put(ClientDatabase.COMPANY_B, companyB);
        this.setTargetDataSources(datasourceMap);
        this.setDefaultTargetDataSource(companyA);
    }
}
public class ClientDatabaseContextHolder {
    private static ThreadLocal<ClientDatabase> threadLocal = new ThreadLocal<>();

    public static void set(ClientDatabase clientDatabase){
        threadLocal.set(clientDatabase);
    }

    public static ClientDatabase getClientDatabase(){
        return threadLocal.get();
    }

    public static void clear(){
        threadLocal.remove();
    }
}
@Component
public class DataSourceInterceptor extends WebRequestHandlerInterceptorAdapter{

    public DataSourceInterceptor(WebRequestInterceptor requestInterceptor) {
        super(requestInterceptor);
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String company = request.getHeader("company");
        if (ClientDatabase.COMPANY_A.toString().equals(company)){
            ClientDatabaseContextHolder.set(ClientDatabase.COMPANY_A);
        } else if(ClientDatabase.COMPANY_B.toString().equals(company)){
            ClientDatabaseContextHolder.set(ClientDatabase.COMPANY_B);
        } else {
            ClientDatabaseContextHolder.set(ClientDatabase.COMPANY_A);
        }
        return super.preHandle(request, response, handler);
    }
}
Configuration
@EnableJpaRepositories(basePackages = "com.tkg.MasterSystem.repositories", transactionManagerRef = "transcationManager", entityManagerFactoryRef = "entityManager")
@EnableTransactionManagement
public class DatasourceRoutingConfiguration {
    @Bean
    @Primary
    @Autowired
    public DataSource dataSource() {
        ClientDataSourceRouter clientDataSourceRouter = new ClientDataSourceRouter();
        clientDataSourceRouter.initDataSource(companyADatasource(), companyBDatasource());
        return clientDataSourceRouter;
    }

    @Bean
    @ConfigurationProperties(prefix = "twt.datasource")
    public DataSource companyADatasource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "tkg.datasource")
    public DataSource companyBDatasource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "entityManager")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dataSource()).packages(User.class).build();
    }

    @Bean(name = "transcationManager")
    public JpaTransactionManager transactionManager(
            @Autowired @Qualifier("entityManager") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryBean.getObject()));
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    DataSourceInterceptor dataSourceInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}
@Entity(name = "users")
@Table(name = "users")
@Data
public class User implements UserDetails {

    public User() {}

    public User(String title, String firstName, String lastName, String middleName, String email, String password, LocalDate dob,  String avatarUrl,  Boolean status, String countryCode, String phoneNumber, String authenticationCode, LocalDateTime sendAuthenticationCodeAt, Long createdBy) {
        this.title = title;
        this.firstName = firstName;
        this.lastName = lastName;
        this.middleName = middleName;
        this.email = email;
        this.password = password;
        this.dob = dob;
        this.avatarUrl = avatarUrl;
        this.status = status;
        this.countryCode = countryCode;
        this.phoneNumber = phoneNumber;
        this.authenticationCode = authenticationCode;
        this.sendAuthenticationCodeAt = sendAuthenticationCodeAt;
        this.createdBy = createdBy;
    }

    public User(StoreUserRequest request){
        this.title = request.getTitle();
        this.firstName = request.getFirstName();
        this.lastName = request.getLastName();
        this.middleName = request.getMiddleName();
        this.dob = request.getDob();
        this.position = request.getPosition();
        this.department = request.getDepartment();
        this.email = request.getEmail();
        this.countryCode = request.getCountryCode();
        this.phoneNumber = request.getPhoneNumber();
        this.avatarUrl = request.getAvatarUrl();
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
        this.createdBy = Helper.getAuthUser().getId();
    }

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String firstName;

    private String lastName;

    private String middleName;

    private String email;

    private String password;

    private LocalDate dob;

    private String position;

    private String department;

    private String avatarUrl;

    private Boolean status;

    private String countryCode;

    private String phoneNumber;

    private String authenticationCode;

    private LocalDateTime sendAuthenticationCodeAt;

    private int numberLoginFailed;

    private String forgotPasswordToken;

    private Long createdBy;

    private LocalDateTime createdAt = LocalDateTime.now();

    private LocalDateTime updatedAt;

    private Long deletedBy;

    private LocalDateTime deletedAt;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return status;
    }

    @Getter(AccessLevel.NONE)
    @OneToMany(mappedBy = "userCreate")
    private List<Permission> permissions;

    @Getter(AccessLevel.NONE)
    @OneToMany(mappedBy = "userCreate")
    private List<Role> roles;

}
@Repository
public interface UserRepositoryInterface extends JpaRepository<User, Long> {
    boolean existsByEmail(String email);

    Optional<User> findByEmail(String email);

    Optional<User> findByForgotPasswordToken(String token);
}
mp911de commented 11 months ago

I'm unable to reproduce the exception from the code you provided. Can you revisit your arrangement and provide a minimal yet complete sample that reproduces the problem? You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

KhanamDEV commented 11 months ago

@mp911de thanks for reply, i resolved my problem. This is my error.

mp911de commented 11 months ago

Thanks for coming back. Out of curiosity, can you share a bit on how you managed to create that exception?

hohwille commented 8 months ago

Thanks for coming back. Out of curiosity, can you share a bit on how you managed to create that exception?

@KhanamDEV that would indeed have been great as I just got the exact same error message and such help would be highly appreciated.

hohwille commented 8 months ago

Just to document my observation:

I am using:

With spring-boot 2.x I always had hibernate-entitymanager as dependency to make it work. However, in the new org.hibernate.orm there are only alpha versions available: https://repo1.maven.org/maven2/org/hibernate/orm/hibernate-entitymanager/ Whether I add this alpha version dependency additionally to hibernate-core or not does not seem to make a difference for the error I am getting. I am slightly confused that migration guides on baeldung, medium nor the hibernate homepage does not say a word about this. However, according to this I see that actually hibernate-core should be just sufficient. Anyhow for me the automagic of spring-boot seems to fail boostraping the entitymanager for hibernate causing this error.

Is there an official spring-data-jpa sample based on the recent spring-boot that I could use to see if that works and then can compare this with my project to figure out the difference?

hohwille commented 8 months ago

Is there an official spring-data-jpa sample based on the recent spring-boot that I could use

https://spring.io/guides/gs/accessing-data-jpa/ https://github.com/spring-guides/gs-accessing-data-jpa/blob/main/complete/pom.xml

hohwille commented 8 months ago

OK, got it: Stupid me, I was missing this dependency:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

Before I had this dependency:

<dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
    </dependency>

So hopefully this may help if others run into the same error.