Instantiate Spring @Service with @Transactional methods manually from Java

Suppose there are interfaces @Service

and @Repository

such as:

@Repository
public interface OrderDao extends JpaRepository<Order, Integer> {

}

public interface OrderService {

    void saveOrder(Order order);

}

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) {
        orderDao.save(order);
    }

}

      

This is part of a working application, everything is configured to access a single database, and everything is working fine.

Now, I would like to be able to create a standalone working instance of OrderService by automatic posting OrderDao using pure Java with the jdbcUrl specified in the Java code , something like this:

final int tenantId = 3578;
final String jdbcUrl = "jdbc:mysql://localhost:3306/database_" + tenantId;
OrderService orderService = someMethodWithSpringMagic(appContext, jdbcUrl);

      

As you can see, I would like to introduce a multi-tenant architecture with a per-database tenant strategy for an existing Spring based application.

Note that I was able to achieve this quite easily before using my own implementation of jdbcTemplate-like logic also with JDBC transactions working correctly, so this is a very important task.

Also note that I need some pretty simple transactional logic to start a transaction, execute multiple requests in a service method within that transaction, and then commit / rollback it on exception.

Most of the solutions on the web regarding multi-user experience with Spring suggest specifying specific persistence units in the xml config AND / OR using annotation-based configuration, which is highly flexible because the whole application must be stopped to add a new url database. xml config / annotation code must be changed and the application is running.

So, basically I'm looking for a piece of code that is able to create @Service

, like Spring does, creates it internally after properties are read from XML config / annotations. I'm also looking into usage ProxyBeanFactory

for this, because Spring uses AOP

to instantiate services (so I think plain good multi-user OOP is not the way to go here).

Is Spring flexible enough to allow for this relatively simple code reuse case ?

Any hints would be much appreciated and if I find a complete answer to this question I'll post it here for future generations :)

+3


source to share


2 answers


HIbernate has out of the box support for multiple tenants , check this out before you try it yourself. Hibernate requires MultiTenantConnectionProvider

and CurrentTenantIdentifierResolver

, for which there are standard implementations out of the box, but you can always write your own implementation. If this is just a schema change, it is actually quite easy to implement (execute the query before returning the connection). Else keep a datasource map and get an instance from that or create a new instance.



About 8 years ago, we already wrote a general solution that was documented here , and the code. This is not hibernating and can be used for basically anything you need to switch. We've used it for DataSource

as well as some internet related stuff (like among others).

+2


source


Creating a transactional proxy for an annotated service is not an easy task, but I'm not sure if you really need it. To select a database for tenantId, I assume you only need to focus on the frontend DataSource

.

For example, with a simple driver driven data source:



public class MultitenancyDriverManagerDataSource extends DriverManagerDataSource {

    @Override
    protected Connection getConnectionFromDriverManager(String url,
            Properties props) throws SQLException {

        Integer tenant = MultitenancyContext.getTenantId();

        if (tenant != null)
            url += "_" + tenant;

        return super.getConnectionFromDriverManager(url, props);
    }

}

public class MultitenancyContext {

    private static ThreadLocal<Integer> tenant = new ThreadLocal<Integer>();

    public static Integer getTenantId() {
        return tenant.get();
    }

    public static void setTenatId(Integer value) {
        tenant.set(value);
    }
}

      

Of course, if you want to use a connection pool, you need to tweak it a bit, for example using a connection pool for each tenant.

+2


source







All Articles