Goal
In order to test the create method of a DAO, I create an instance, insert it in database, flush the entity manager to update the database and then use dbunit to compare table using dataset.
Code
Here is the code, it uses Spring test, DBUnit and JPA (via Hibernate) :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"/WEB-INF/applicationContext-database.xml"})
public class MyEntityTest extends AbstractTransactionalJUnit4SpringContextTests {
#PersistenceContext
protected EntityManager em;
#Autowired
MyEntityDAO myEntityDAO;
#Test
public void createTest() {
// create the entity
MyEntity record = new MyEntity();
record.setData("test");
myEntityDAO.insertNew(record);
// flush to update the database
em.flush();
// get actual dataset from the connection
Session session = em.unwrap(Session.class);
Connection conn = SessionFactoryUtils.getDataSource(
session.getSessionFactory()).getConnection();
DatabaseConnection connection = new DatabaseConnection(conn);
ITable actualTable = connection.createTable("MY_ENTITY");
// get expected dataset
IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(getClass().getResource("dataset.xml"));
ITable expectedTable = expectedDataSet.getTable("MY_ENTITY");
// compare the dataset
Assertion.assertEquals(expectedTable, actualTable);
}
}
Problem
This code never ends, it seems to freeze (infinite loop ?) during this command:
ITable actualTable = connection.createTable("MY_ENTITY");
But if I comment the em.flush() block, the test ends (no freeze or infinite loop). In this case, the test fails because the database has not been updated after the insert.
Question
how can I test the create method of a DAO using a similar approach (compare dataset with dbunit) without having a freeze when calling dataset.getTable() ?
I found the solution. The problem was caused by the connection.
If I replace :
Session session = em.unwrap(Session.class);
Connection conn = SessionFactoryUtils.getDataSource(
session.getSessionFactory()).getConnection();
by
DataSource ds = (DataSource) applicationContext.getBean("dataSource");
Connection conn = DataSourceUtils.getConnection(ds);
All runs fine...
I don't understand why, so let me know in comments if you have any clue to help me understand that.
Related
I would like to open a new transaction and use all the changes which I have made in first transaction. But from second transaction I couldn't read inner Entity
class A {
#Autowirde
private B b;
#Transactional
public test() {
ProgramRole programRole = new ProgramRole();
programRoleRepo.save(programRole);
Program program = new Program();
program.setProgramRole(programRole);
programRepo.save(program); // Let say id is 1
Program p = programRepo.getOne(1);
p.getProgramRole() // Return 'programRole'
b.test();
}
}
#Component
class B {
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
public void test() {
Program p = programRepo.getOne(1);
p.getProgramRole() // Return null. Why?
}
}
Hibernate holds the persistable state in memory, The process of synchronizing this state to the underlying DB is called flushing. When we use the save() method, the data associated with the save operation will not be flushed to the DB unless and until an explicit call to flush() or commit() method is made, In your case Transactional annotation will do that
as b.test method is calling from with in a transaction so your are getting null there as first transaction still in process(transaction will commit data when method execution competed).
For your use case, you need to use saveAndFlush instead of only save
ProgramRole programRole = new ProgramRole();
programRoleRepo.saveAndFlush(programRole);
Program program = new Program();
program.setProgramRole(programRole);
programRepo.saveAndFlush(program); /
I'm doing an integration test in spring and in this example I testing a service layer.
I have a problem where during the addition test in the service, rollback not working, and always add an item to the base ,but not delete it.
I put annotation # Transactional and # TestPropertySource on test class,
also have application-test.properties and the test is successful but the rollback is not executed and a new item is always added to the test database.
My test class and test method for add address(last one):
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TicketServiceApplication.class)
#Transactional
#TestPropertySource("classpath:application-test.properties")
public class AddressServiceIntegrationTest {
#Autowired
private AddressService addressService;
#Autowired
private AddressRepository addressRepository;
#Test
public void findAllSuccessTest(){
List<Address> result = addressService.finfAllAddress();
assertNotNull(result);
assertFalse(result.isEmpty());
assertEquals(2, result.size());
}
#Test
public void findOneAddressExistTest_thenReturnAddress(){
Long id = AddressConst.VALID_ID_ADDRESS;
Address a = addressService.findOneAddress(id);
assertEquals(id, a.getId());
}
#Test(expected = EntityNotFoundException.class)
public void findOneAddressNotExistTest_thenThrowException(){
Long id = AddressConst.NOT_VALID_ID_ADDRESS;
Address a = addressService.findOneAddress(id);
}
#Test
public void addAddressSuccessTest(){
int sizeBeforeAdd = addressRepository.findAll().size();
Address address = AddressConst.newAddressToAdd();
Address result = addressService.addAddress(address);
int sizeAfterAdd = addressRepository.findAll().size();
assertNotNull(result);
assertEquals(sizeBeforeAdd+1, sizeAfterAdd);
assertEquals(address.getCity(), result.getCity());
assertEquals(address.getState(), result.getState());
assertEquals(address.getNumber(), result.getNumber());
assertEquals(address.getLatitude(), result.getLatitude());
assertEquals(address.getLongitude(), result.getLongitude());
assertEquals(address.getStreet(), result.getStreet());
}
}
My application-test.properties :
spring.datasource.url= jdbc:mysql://localhost:3306/kts_test&useSSL=false&
useUnicode=true&characterEncoding=utf8
spring.datasource.username = root
spring.datasource.password = root
spring.jpa.show-sql = true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
And when execute add address test, every time in my kts_test data base ( db used for testing) is added new item and not rollback.
This is a log from the console where you can see that the rollback was called but did not execute, because when I refresh the database after the test the new item was left, it was not deleted.
INFO 10216 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext#3e58a80e testClass = AddressServiceIntegrationTest, testInstance = com.ftn.services.address.AddressServiceIntegrationTest#4678ec43, testMethod = addAddressSuccessTest#AddressServiceIntegrationTest, testException = [null],...
Lastly, to write that I tried #Transactional above methods, I also tried # Rollback above methods or # Rollback (true), tried changing application-test.properties and I no longer know what the error might be.
If anyone can help I would be grateful. Thank you.
Hibernate is by default creating tables with MyISAM storage engine - this engine does not support transactions. You need to have InnoDB tables:
Either manage your DB migrations manually with a tool like Flyway (imho preferred option so you can have a complete control) and turn off recreating database in tests.
Or set the correct engine in configuration:
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
OR (deprecated)
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
I am using Spring boot application, on that i am trying to achieve Transactional management. But Spring doesn't rollback the data which saved in same method.
Code base: https://github.com/vinothr/spring-boot-transactional-example
Can any one help me?
This is my repository class for 'Test' entity.
#Repository
public interface TestRepository extends CrudRepository<com.example.demo.Test, Long> {
}
I have created one end-point which used to save the data to 'Test' entity. After save happen, I thrown RunTimeException, but it is not rollbacking the saved value
#GetMapping("/test")
#Transactional
public void create() {
System.out.println("test");
final Test p = createTest();
testRepository.save(p);
final Test p1 = createTest();
testRepository.save(p1);
throw new RuntimeException();
}
It works fine after I changed into 'InnoDB' engine because I was using 'MyISAM' engine which doesn't support transaction.
ALTER TABLE my_table ENGINE = InnoDB;
Try indicate #Transactional(rollbackFor = RuntimeException.class)
The below code is not working for rollback when any exception occurs while insertion of records in database.I am using Spring 4 framework and annotation .
*/I am using below code for transaction management and it will not roll back for any exception./
#Transactional(rollbackFor = RuntimeException.class)
public boolean insertBatch(List<String> query) throws SQLException {
boolean flag= false;
try
{
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String[] Sql= query.toArray(new String[query.size()]);
jdbcTemplate.batchUpdate(Sql);
flag=true;
}catch(DataAccessException e )
{
flag=false;
MessageResource.setMessages("Constraints Violation ! CSV data value not matched with database constraints ");
LOGGER.info("CSV file Data not expected as database table structure defination like constraint violation/Data Type lenght/NUll etc for same data value" );
LOGGER.error( "Cause for error: "+ e.getRootCause().getMessage());
LOGGER.debug( "Details explain : "+ e.toString());
throw new RuntimeException("Roll back operation");
//transactionManager.rollback(status);
}
return flag;
}**
Actullay answaer provided by Sir, M.Deinum is below:
Spring uses proxies to apply AOP this will only work for methods called from the outside. Internal method calls don't pass through the proxy hence no transactions and depending on your queries you get one large or multiple smaller commits. Make sure that the outer method (the one called to initiate everything) is transactional. – M. Deinum 14 hours ago
#Transactional(rollbackFor = RuntimeException.class)
This will rollback only if a RuntimeException or a subclass is thrown from the annotated method. If you want to rollback for any Exception (such as SQLException, which is NOT a RuntimeException), you should do:
#Transactional(rollbackFor = Exception.class)
And if you want to try a rollback for whatever error that might happen
#Transactional(rollbackFor = Throwable.class)
Altough in this last case the runtime might be so broken that not even the rollback can complete.
Use Prepared statement from connection object and the do a execute batch object. On the connection object use conn.setAutoCommit(false). Prepeared statement has 4 times better performance than JdbcTemplate for batch insertion of 1000 records.
Reference : JdbcTemplate.batchUpdate() returns 0, on insert error for one item but inserts the remaining item into sql server db despite using #Transactional
I am using HSQLDB for unit testing, I have bunch of Test classes which all extends one Abstract class MyAbstractTestBase.
class abstract MyAbstractTestBase
{
static
{
setUpDBTables();
}
public static void setUpDBTables()
{
context = new ClassPathXmlApplicationContext(new String[]{
"file:spring-configuration/unit-testing-config.xml"
});
//InputStream inputStream = getClass().getClassLoader().getResourceAsStream("db/MY_TABLE.sql");
DataSource dataSource = (DataSource)context.getBean("MyDataSource");
namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
}
Major problem with this approach is entityManager created by spring,
#PersistenceContext(unitName = MyConstants.ENTITY_MANAGER_FACTORY_UNIT_NAME)
protected EntityManager entityManager;
doesn't persist data and no exception is thrown either, but if I try to read some data using "select" it works.
Here is my question, How do I create tables before starting unit-tests? So that my entityManager will work as expected.
Also, why did my entityManager did not persist any record even though it did not throw any exception.
If you're using spring test, I strongly suggest using the approach from this answer: How to load DBUnit test data once per case with Spring Test