-1

I'm working on a Spring Boot application and I'm trying to inject my UserService class into my TenantIdentifierResolver class because I want to use the createUser() method. However i get a nullpointer exception. For some reason userService is set to null, what am I missing here?

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

    public static final String DEFAULT_TENANT = "default_schema";

    private UserService userService;

    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenant =  TenantContext.getCurrentTenant();
        if(tenant != null){
            userService.createUser(tenant);
            return tenant;
        } else {
            return DEFAULT_TENANT;
        }
    }

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

I've tried to use @Autowired or make a constructor injection but then I got this error:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  entityManagerFactory defined in class path resource [org/example/membership/config/HibernateConfig.class]
↑     ↓
|  tenantIdentifierResolver (field private org.example.membership.service.UserService org.example.membership.multitenancy.TenantIdentifierResolver.userService)
↑     ↓
|  userService defined in file [C:\project\Member\server\target\classes\org\example\membership\service\UserService.class]
↑     ↓
|  userRepository defined in org.example.membership.repository.UserRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration
↑     ↓
|  (inner bean)#18957a3d
└─────┘

This is my UserService class

@Service
public class UserService {

    private UserRepository userRepository;
    private TenantService tenantService;

    public UserService(UserRepository repository, TenantService tenantService) {
        this.userRepository = repository;
        this.tenantService = tenantService;
    }

    @Transactional
    public User createUser(String tenant) {
        Tenant tenant = new Tenant();
        tenant.setTenantName(tenant);
        tenantService.initDatabase(tenant);
        return tenant ;
    }
}

This is my HibernateConfig class

@Configuration
public class HibernateConfig {
    @Autowired
    private JpaProperties jpaProperties;

    @Bean
    JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

        @Bean
        LocalContainerEntityManagerFactoryBean entityManagerFactory(
        DataSource dataSource,
        MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
        CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl
    ) {

        Map<String, Object> jpaPropertiesMap = new HashMap<>(jpaProperties.getProperties());
      jpaPropertiesMap.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
      jpaPropertiesMap.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);       
      jpaPropertiesMap.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);

        LocalContainerEntityManagerFactoryBean em = new 
LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("org.example*");
        em.setJpaVendorAdapter(this.jpaVendorAdapter());
        em.setJpaPropertyMap(jpaPropertiesMap);
        return em;
    }
}
TheStranger
  • 1,387
  • 1
  • 13
  • 35
  • 1
    Did you try `@Autowired` on the service? Currently there's no injection at all. – Thomas May 12 '21 at 07:54
  • @Thomas Yes, I've tried that, but then it gives the following error: "The dependencies of some of the beans in the application context form a cycle" – TheStranger May 12 '21 at 07:55
  • 1
    Show your UserService – EricSchaefer May 12 '21 at 07:57
  • @EricSchaefer I've updated my initial post with the UserService class. – TheStranger May 12 '21 at 08:08
  • You could try with field/attribute injection, i.e. have `UserService` also use `@Autowired` instead of constructor injection. Or try to add `@Lazy` to your constructor parameters. See here for some more info: https://www.baeldung.com/circular-dependencies-in-spring – Thomas May 12 '21 at 08:10
  • @Thomas When I do that it gives the cycle error mentioned above, in the post. – TheStranger May 12 '21 at 08:14
  • Did read the article and try the different approaches there? If none works you'd probably need to rethink your design to remove the cycle. A last option might be to do a lookup in one of the components instead of using injection. – Thomas May 12 '21 at 08:35
  • Can you show the code of `HibernateConfig` please? – dunni May 12 '21 at 08:47
  • @dunni I've added my HibernateConfig. – TheStranger May 12 '21 at 08:54

4 Answers4

1
@Autowired
private UserService userService;

As for the circular dependency problem, I think you should try to avoid.

sevenguin
  • 23
  • 7
0

You are not injecting the dependency. You should use Constructor injection:

private final UserService userService;

public TenantIdentifierResolver(UserService userService) {
    this.userService = userService;
}

Or if you prefer you can use attribute injection:

@Autowired
private UserService userService;
marc-gil
  • 51
  • 2
  • I've updated my initial post. I have tried to do what you are suggesting already but i get the cycle error as you can see above. I've tried to google it but I'm not sure I understand what the error is. – TheStranger May 12 '21 at 08:10
0

The problem you are facing here is called circular dependency. It occurs when two components need bean of each other. Spring doesn't know which one to create first. You can read more about it here www.netjstech.com/2018/03/circular-dependency-in-spring-framework.html

Easiset solution would be to implement setter-based auto wiring in one of components.

@Component
public class TenantIdentifierResolver {

   private UserService userService;

   public UserService userService() {
       return userService;
   }

   @Autowired
   public void setUserService(final UserService userService) {
       this.userService = userService;
   }
}
mihajlo
  • 71
  • 5
0

In the end you have to resolve the circular dependency, no matter what injection types you use or if you resort to lazy injection etc.

At the moment your entityManagerFactory requires the userService bean (through the tenantIdentifierResolver), which in itself requires entityManagerFactory. That's a bad architecture. You can either remove the call to userService from the tenantIdentifierResolver, or change it so that no JPA related functionality is used in there (i.e. if you really need to create the tenants/users on the fly, use plain JDBC calls in there). Then you have broken the dependency cycle and everything should work.

dunni
  • 43,386
  • 10
  • 104
  • 99