Spring Boot and Spring-starter-data-jpa : Lazy loading of datasource setup - spring

I am working on an application with Spring-starter-data-jpa and multi datasources, that need to lazy initialize datasouce connection, but i dosen't manage to work with it.
For exemple if i put a wrong password on a datasource configuration, the application startup fails.
I try to put a #Lazy annotation on the Datasource #Configuration class but the application still crash on startup.
I try to implement a LazyConnectionDataSourceProxy instead the Datasource without success.
I can't imagine that it is not possible.
Is somewone have an idea to how lazy load datasources configuration on the first jpa repository call ?
I tryed to do this with a kind of adapter/singleton mix pattern like this :
#Component
public class TestErrorDatasource implements DataSource {
private DataSource datasource;
#Autowired Environment env;
#Override
public PrintWriter getLogWriter() throws SQLException {
return getDatasource().getLogWriter();
}
#Override
public int getLoginTimeout() throws SQLException {
return getDatasource().getLoginTimeout();
}
#Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return getDatasource().getParentLogger();
}
#Override
public void setLogWriter(PrintWriter arg0) throws SQLException {
getDatasource().setLogWriter(arg0);
}
#Override
public void setLoginTimeout(int arg0) throws SQLException {
getDatasource().setLoginTimeout(arg0);
}
#Override
public boolean isWrapperFor(Class<?> arg0) throws SQLException {
return getDatasource().isWrapperFor(arg0);
}
#Override
public <T> T unwrap(Class<T> arg0) throws SQLException {
return getDatasource().unwrap(arg0);
}
#Override
public Connection getConnection() throws SQLException {
return getDatasource().getConnection();
}
#Override
public Connection getConnection(String arg0, String arg1) throws SQLException {
return getDatasource().getConnection(arg0, arg1);
}
private DataSource getDatasource() {
if(datasource == null) {
this.datasource = DataSourceBuilder
.create()
.driverClassName(env.getProperty("testError.datasource.driverClassName"))
.url(env.getProperty("testError.datasource.url"))
.username(env.getProperty("testError.datasource.username"))
.password(env.getProperty("testError.datasource.password"))
.build();
}
return datasource;
}
}
But it dosen't work.
I put a break point on the getConnection() methode and i raised tha it's called on startup by
org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl
class
Is there any configuration to avoid hibernate check the getConnection() methode on starup ?
Thanks in advance

A datasource is just an interface with a 'getConnection()' method on it.
So create your own implementation that delegates to a singleton that is created only when getConnection is first called.
In other words, whilst your implementation can be a Spring bean, its singleton dependency isn't, and isn't injected, but set the first time you access getConnection.

Related

How to mock bean HikariDataSource correctly?

I wrote integration test using Mockito, but it works when connection to database was set. Actually test just check possibility access some endpoints and not related to the data access layer. So I don't need database for it yet.
Reason of failing test when database is down - HikariDatasource check connection to the database when spring instantiates context. Mocking doesn't return Connection and it lead to the fail of application. Solution that i have found is use hsql in memory database, but for me it looks like work around. Probably exists other solution providing some fake data?
Not sure that this is elegant solution, but I need to force work tests like this
mockMvc.perform(
post("/some").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(someDto))
.header(HttpHeaders.AUTHORIZATION, AUTH_HEADER)
.accept(MediaType.APPLICATION_JSON_UTF8)
).andExpect(status().is(201));
After debugging and searching I have found solution that allowed start container without database in memory.
#TestConfiguration
#ComponentScan(basePackages = "com.test")
#ActiveProfiles("test")
public class TestConfig {
//Other Beans
#Bean
public DataSource getDatasource() {
return new MockDataSource();
}
}
class MockDataSource implements DataSource {
#Override
public Connection getConnection() throws SQLException {
return createMockConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
#Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
#Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
#Override
public void setLoginTimeout(int seconds) throws SQLException {
}
#Override
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
#Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
#Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public static Connection createMockConnection() throws SQLException {
// Setup mock connection
final Connection mockConnection = mock(Connection.class);
// Autocommit is always true by default
when(mockConnection.getAutoCommit()).thenReturn(true);
// Handle Connection.createStatement()
Statement statement = mock(Statement.class);
when(mockConnection.createStatement()).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.isValid(anyInt())).thenReturn(true);
// Handle Connection.prepareStatement()
PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (int[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (String[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
doAnswer((Answer<Void>) invocation -> null).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
ResultSet mockResultSet = mock(ResultSet.class);
when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
when(mockResultSet.getString(anyInt())).thenReturn("aString");
when(mockResultSet.next()).thenReturn(true);
// Handle Connection.prepareCall()
CallableStatement mockCallableStatement = mock(CallableStatement.class);
when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
ResultSet mockResultSetTypeInfo = mock(ResultSet.class);
DatabaseMetaData mockDataBaseMetadata = mock(DatabaseMetaData.class);
when(mockDataBaseMetadata.getDatabaseProductName()).thenReturn("PostgreSQL");
when(mockDataBaseMetadata.getDatabaseMajorVersion()).thenReturn(8);
when(mockDataBaseMetadata.getDatabaseMinorVersion()).thenReturn(2);
when(mockDataBaseMetadata.getConnection()).thenReturn(mockConnection);
when(mockDataBaseMetadata.getTypeInfo()).thenReturn(mockResultSetTypeInfo);
when(mockConnection.getMetaData()).thenReturn(mockDataBaseMetadata);
// Handle Connection.close()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close();
// Handle Connection.commit()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already committed")).when(mockConnection).commit();
// Handle Connection.rollback()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already rolled back")).when(mockConnection).rollback();
return mockConnection;
}
}
Mocking DataSource allows start container and provide post call to the controller using MockMvc.

Rollback is not working for multiple insert

#Transactional annotation on a method inside the Application class is not rolling back insertions. However, #Transactional annotation on the service class(EmpService.java) method("insertEmp(Emp emp)") is working as expected.
Could someone please let me know why #Transactional working differently?
Spring Boot version - 2.1.3.RELEASE with the h2 database.
Please let me know if any additional information required.
#SpringBootApplication
#ComponentScan("org.saheb")
#EnableJpaRepositories("org.saheb.repo")
#EntityScan("org.saheb.vo")
#EnableTransactionManagement
public class SpringJpaTransactionApplication implements CommandLineRunner {
#Autowired
private EmpService empService;
public static void main(String[] args) {
SpringApplication.run(SpringJpaTransactionApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
insertSingleBatch();
}
#Transactional(rollbackFor=RuntimeException.class, propagation=Propagation.REQUIRES_NEW)
public void insertSingleBatch() {
try {
Set<Emp> empSet = new LinkedHashSet<>();
Dept dept = new Dept();
dept.setDeptNo(10);
empSet.add(new Emp("abc", "abc", dept));
empSet.add(new Emp("xyz", "xyz", dept));
empSet.add(new Emp("def", "def", dept));
empSet.add(new Emp("pqrstu", "pqr", dept));// This will fail as max character allowed in 5 and should rollback all the insertion. But, first three records are getting saved in h2 database.
empService.insertEmp(empSet);
} catch (RuntimeException e) {
System.out.println("Exception in batch1.." + e.getMessage());
}
}
}
#Service
public class EmpService {
#Autowired
private EmpRepository empRepository;
//#Transactional(rollbackFor=RuntimeException.class, propagation=Propagation.REQUIRES_NEW)//This is working as expected as all the insertions are rolling back after failure of 4th insertion
public void insertEmp(Set<Emp> empSet) {
System.out.println("Inside insert");
for (Emp temp : empSet) {
Emp temp2 =empRepository.save(temp);
System.out.println("inserted-->"+temp2.getFirstName());
}
}
}
You are "self-invocation" a #Transactional method from the same bean which will not work .This behaviour is well explained in the docs at here and here (Search the keyword "self-invocation")
You can simply move the #Transactional method to another bean.Then inject this bean to its client bean and invoke this #Transactional method.
Or use the TransactionTemplate to execute it within a transaction:
#Autowired
private TransactionTemplate txTemplate;
#Override
public void run(String... args) throws Exception {
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(status->{
insertSingleBatch();
return null;
});
}
Please note that TransactionTemplate will ignore the setting on the #Transactional and you have to configure it programatically.

Problems using dbunit with Spring (without spring-test-dbunit)

I'm trying to use dbunit to test my DAOs. We use Spring in a version that is not compatible with spring-test-dbunit. I can't autowire my dao beans into my test class, because then I would have to use #RunWith(SpringJUnit4ClassRunner.class) which regards one parameterless constructor. My class looks like following:
public class DbUnitExample extends DBTestCase {
#Autowired
public MyDAO myDAO;
public DbUnitExample(String name) {
super(name);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "...");
}
#Override
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSetBuilder().build(new FileInputStream("target/partial.xml"));
}
#Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.REFRESH;
}
#Override
protected DatabaseOperation getTearDownOperation() throws Exception {
return DatabaseOperation.NONE;
}
#Test
public void testSometing() throws Exception {
myDAO.deleteById(12662);
}
}
Of course I get an NPE because my dao bean can't be found. When I use #RunWith(SpringJUnit4ClassRunner.class) I need to provide one parameterless constructor and have to delete my "dbunit"-constructor. Is there a standard way or workaround to use dbunit with spring without the use of spring-test-dbunit
EDIT
My class now looks like following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/test-application.xml")
#DirtiesContext
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
public class DbUnitExample extends DBTestCase {
#Autowired
public MyDAO myDAO;
public DbUnitExample() {
super("target/partial.xml");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "...");
}
#Override
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSetBuilder().build(new FileInputStream("target/partial.xml"));
}
#Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.REFRESH;
}
#Override
protected DatabaseOperation getTearDownOperation() throws Exception {
// return DatabaseOperation.NONE;
// return DatabaseOperation.REFRESH;
return DatabaseOperation.CLEAN_INSERT;
}
#Test
public void testSometing() throws Exception {
myDAO.deleteById(12662);
}
}
It compiles now, but has no dbunt-functionality, which means if I delete a row it doesn't get restored to it's previous state (inserted again).
Since you are using Spring, I suggest autowiring the dbUnit instances into the test. The dbUnit Test Cases page has "Configuration Example Using Spring" for the PrepAndExpectedTestCase, but just copy the code and change it to DBTestCase and adjust accordingly.

MultiTenantConnectionProvider implementation has null autowired datasource

I'm trying to support a multi-tenant by schema in my spring boot (1.4) application. I have the following in my config:
hibernate:
format_sql: true
default_schema: corrto
multiTenancy: SCHEMA
tenant_identifier_resolver: com.config.HeaderTenantIdentifierResolver
multi_tenant_connection_provider: com.config.SchemaPerTenantConnectionProvider
My MultiTenantConnectionProvider implementation is as follows:
public class SchemaPerTenantConnectionProvider implements MultiTenantConnectionProvider {
#Autowired
private DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return this.dataSource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = this.getAnyConnection();
// need to do stuff here
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}
It is failing because dataSource is null. I'm assuming it hasn't been created yet but I'm having a hard time finding solutions via Google.
I met the same problem.It seems that in the yml ,HeaderTenantIdentifierResolver and SchemaPerTenantConnectionProvider is managed by hibernate.See here.

Unittest a transactional method with REQUIRES_NEW and always force rollback

I want to test a service-method that inserts data into a table by calling a DAO in a loop. The service-method is annotated with
#Transactional(propagation = Propagation.REQUIRES_NEW)
The unit-test calls the service-method and is annotated with
#Transactional
Now I would like to tell the transaction that it always should do a rollback at the end. I don't want to clean up the db manually after the testruns.
#Rollback and EntityManager.getTransaction().setRollbackOnly() does'nt work. I think the reason is that the annotation and setRollbackOnly() are only applied on the Transaction that is created by the test-method and not on the transaction that is created by the service-method.
Does anyone know how to solve that?
I don't think it's possible to easily rollback a REQUIRES_NEW transaction. SpringTest starts a transaction and it can rollback the transaction that it started. But not the transactions started inside.
So you either may fall back to REQUIRED or write your tests to work fine even if they commit. If you choose the latter, you can achieve test isolation via randomization.
You can probably create a controllable mock implementation of org.springframework.transaction.PlatformTransactionManager delegating to the real one with only single difference that it would optionally mark a new transaction as read-only. Something like that if using java-based context configuration:
...
public class DaoTest {
...
#Autowired
Dao _dao;
#Autowired
MockPlatformTransactionManager _transactionManager;
...
#Test
#Transactional
public void testSomeAction() throws Exception
{
try (AutoCloseable ignored = _transactionManager.withRollBack())
{
_dao.someAction();
}
}
...
interface MockPlatformTransactionManager extends PlatformTransactionManager
{
AutoCloseable withRollBack();
}
...
// Somewhere in the #Configuration class
#Bean
MockPlatformTransactionManager transactionManager()
{
// an instance of real TM is adapted to become a MockPlatformTransactionManager
return new MockPlatformTransactionManager()
{
private final PlatformTransactionManager _delegate =
// TODO: same transaction manager as before
new DataSourceTransactionManager(dataSource());
private boolean shouldRollBack = false;
#Override
public TransactionStatus getTransaction(final TransactionDefinition definition)
throws TransactionException
{
final TransactionStatus transaction = _delegate.getTransaction(definition);
if (shouldRollBack)
transaction.setRollbackOnly();
return transaction;
}
#Override
public void commit(final TransactionStatus status) throws TransactionException
{
_delegate.commit(status);
}
#Override
public void rollback(final TransactionStatus status) throws TransactionException
{
_delegate.rollback(status);
}
#Override
public AutoCloseable withRollBack()
{
shouldRollBack = true;
return new AutoCloseable()
{
#Override
public void close() throws Exception
{
shouldRollBack = false;
}
};
}
};
}
If you use a PlatformTransactionManager implementation that permits to set the UserTransaction (like org.springframework.transaction.jta.JtaTransactionManager) you can instantiate an UserTransaction implementation which commit method does a rollback.
import com.atomikos.icatch.jta.UserTransactionImp;
#Bean
public UserTransaction myUserTransaction() {
final UserTransactionImp userTransactionImp = new UserTransactionImp() {
#Override
public void commit() throws javax.transaction.RollbackException, javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException, javax.transaction.SystemException, java.lang.IllegalStateException,
java.lang.SecurityException {
rollback();
}
};
return userTransactionImp;
}
And then, in your PlatformTransactionManager bean:
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier("myUserTransaction") UserTransaction myUserTransaction,
#Qualifier("myTransactionManager") TransactionManager myTransactionManager
) {
final JtaTransactionManager jtaTm = new JtaTransactionManager();
jtaTm.setTransactionManager(myTransactionManager);
jtaTm.setUserTransaction(myUserTransaction);
return jtaTm;
}
I've also made my dao does not make any flush.

Resources