I'm trying to test my dao, that uses jdbcTemplate.batchUpdate method under the hood.
My tests are run against real datasource and all methods are performed in transactions marked as rollback-only (any changes are rolled back after the test). Test transactions are managed by PlatformTransactionManager.
The issue here, is that jdbcTemplate.batchUpdate seems to be executed in separate transaction started by DataSourceTransactionManager and thus, I can't see changes made by jdbcTemplate.
My test:
#Transactional
#TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
#RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractDbUnitTest
...
#Test
public void removeSpecific() {
myDao.removeSpecific(myDao.findAllAliasedItems());
Assert.assertEquals(0, myDao.findAllAliasedItems().size());
}
Dao
#Override
public void removeSpecific(final List<? extends Item> items) {
jdbcTemplate.batchUpdate("delete from ITEM where item_type = ? and item_id = ?", new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, items.get(i).getType().name());
ps.setString(2, items.get(i).getId(););
}
#Override
public int getBatchSize() {
return items.size();
}
});
}
Is there any way to test batchUpdate method item without actually altering the data?
Thanks in advance.
I'm sorry for misleading you all. The issue was not caused by nested transaction.
This question is a result of my total misunderstanding of the hierarchy of transactions-related classes in Java and how batch statements in JDBC implemented.
Test was failing because subsequent calls to
myDao.findAllAliasedItems()
were cached. Spring JDBC template worked just fine and as one might expect.
Related
I'm new to Transactional Management, and I have a requirement that I might have to update the same column in DB with in the same call..
Here is what I have :
#Override
public void updateData(Keys keys) {
update1(keys);
update2(keys);
}
#Transactional
private void update1(Keys kesy) {
if(StringUtils.isNotBlank(keys.getValue1())) {
repo.updateKey1(keys.getValue1());
}
}
#Transactional
private void update2(Keys keys) {
if(StringUtils.isNotBlank(keys.getValue2())) {
repo.updateKey2(keys.getValue2());
}
}
I wrote it like this because I might get the same result for both methods, and I want to commit the data every time and get the lastest data
Any help is much appriciated.
I'm not sure if i understood your question right, but if you want to have both update calls in one transaction, it would be enough to annotate the updateData method with #Transactional:
#Override
#Transactional
public void updateData(Keys keys) {
update1(keys);
update2(keys);
}
It appears that the update for mongoOperations do not trigger the events in AbstractMongoEventListener.
This post indicates that was at least the case in Nov 2014
Is there currently any way to listen to update events like below? This seems to be quite a big omission if it is the case.
MongoTemplate.updateMulti()
Thanks!
This is no oversight. Events are designed around the lifecycle of a domain object or a document at least, which means they usually contain an instance of the domain object you're interested in.
Updates on the other hand are completely handled in the database. So there are no documents or even domain objects handled in MongoTemplate. Consider this basically the same way JPA #EntityListeners are only triggered for entities that are loaded into the persistence context in the first place, but not triggered when a query is executed as the execution of the query is happening in the database.
I know it's too late to answer this Question, I have the same situation with MongoTemplate.findAndModify method and the reason I needed events is for Auditing purpose. here is what i did.
1.EventPublisher (which is ofc MongoTemplate's methods)
public class CustomMongoTemplate extends MongoTemplate {
private ApplicationEventPublisher applicationEventPublisher;
#Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher
applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
//Default Constructor here
#Override
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
T result = super.findAndModify(query, update, entityClass);
//Publishing Custom Event on findAndModify
if(result!=null && result instanceof Parent)//All of my Domain class extends Parent
this.applicationEventPublisher.publishEvent(new AfterFindAndModify
(this,((Parent)result).getId(),
result.getClass().toString())
);
return result;
} }
2.Application Event
public class AfterFindAndModify extends ApplicationEvent {
private DocumentAuditLog documentAuditLog;
public AfterFindAndModify(Object source, String documentId,
String documentObject) {
super(source);
this.documentAuditLog = new DocumentAuditLog(documentId,
documentObject,new Date(),"UPDATE");
}
public DocumentAuditLog getDocumentAuditLog() {
return documentAuditLog;
}
}
3.Application Listener
public class FindandUpdateMongoEventListner implements ApplicationListener<AfterFindAndModify> {
#Autowired
MongoOperations mongoOperations;
#Override
public void onApplicationEvent(AfterFindAndModify event) {
mongoOperations.save(event.getDocumentAuditLog());
}
}
and then
#Configuration
#EnableMongoRepositories(basePackages = "my.pkg")
#ComponentScan(basePackages = {"my.pkg"})
public class MongoConfig extends AbstractMongoConfiguration {
//.....
#Bean
public FindandUpdateMongoEventListner findandUpdateMongoEventListner(){
return new FindandUpdateMongoEventListner();
}
}
You can listen to database changes, even the changes completely outside your program (MongoDB 4.2 and newer).
(code is in kotlin language. same for java)
#Autowired private lateinit var op: MongoTemplate
#PostConstruct
fun listenOnExternalChanges() {
Thread {
op.getCollection("Item").watch().onEach {
if(it.updateDescription.updatedFields.containsKey("name")) {
println("name changed on a document: ${it.updateDescription.updatedFields["name"]}")
}
}
}.start()
}
This code only works when replication is enabled. You can enable it even when you have a single node:
Add the following replica set details to mongodb.conf (/etc/mongodb.conf or /usr/local/etc/mongod.conf or C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg) file
replication:
replSetName: "local"
Restart mongo service, Then open mongo console and run this command:
rs.initiate()
I have a Listener with #PostPersist method called "doAudit" to audit create action.On debugging I see This method gets called and Audit record is created on JAVA side. But when I verify the DB I don't see the record.
public class AuditListener {
#PostPersist
public void doAudit(Object object) {
AuditDao auditManager = AuditDaoImpl.getInstance();
auditManager.logEvent("create", object);
}
}
public interface AuditDao {
#Transactional(propagation= Propagation.REQUIRED)
public AuditEntity logEvent(String action, Object object);
}
#Component
public class AuditDaoImpl implements AuditDao {
private static AuditDaoImpl me;
public AuditDaoImpl() {
me = this;
}
public static AuditDaoImpl getInstance() {
return me;
}
#Autowired
private AuditDao dao;
#Transactional
#Override
public AuditEntity logEvent(String action, Object object) {
AuditEntity act = new AuditEntity();
act.setAction(action);
act.setObject(object);
dao.create(act);
return act;
}
}
I am using Open JPA 2.0 as for my ORM. Deployed on karaf container. I am using Postgres SQL as my backend.
Add a debug breakpoint and check if the current stack-trace contains the TransactionInterceptor. If there's no such entry, the Spring transaction management is not properly configured and your DAOs don't use transactions at all.
JPA allows you to run queries without configuring transactions explicitly. For saving data, transactions are mandatory.
I have my entities with a #version column, daos, and junit test.
How can i induce an optimistic lock exception in the junit test case, to see that it's handled correctly?
I am using spring transaction managamemnt, so this makes it more complicated i think
Open a transaction from a jUnit test method and read one row from a certain table.
Create a new thread and open another database transaction which will read the same row.
Update it, and save it to the database.
Pause the main thread used by the jUnit test method.
Modify the data read at the beginning and try updating the row. As a result an optimistic lock exception should be thrown.
In my current Project we have to handle the OptimisticLockException and wrap it into a coustomized exception. Instead of Spring we are using hibernate. But maybe this way will help you.
For my solution you need an OpenEJB-Container in your Test:
#LocalClient
public class ExampleClassTest {
//its a self written class to bootstrap the open ejb container
private static OpenEjbContainerStarter openEjbStarter;
private static Context context;
#Resource
private UserTransaction userTransaction;
#EJB
private ExampleWithSaveActionFacade facade;
#EJB
private ExampleDAO exampleDataAccessObject;
#BeforeClass
public static void setUpBefore() throws Exception {
openEjbStarter = new OpenEjbContainerStarter();
context = openEjbStarter.startOpenEJB();
}
#AfterClass
public static void shutdown() throws NamingException, SQLException {
openEjbStarter.destroyOpenEJB();
}
#Before
public void before() throws Exception {
context.bind("inject", this);
}
#Test(expected = OptimisticLockException.class)
public void testSaveSomethingWithException(){
//get the first object you will manipulate and change the Version
//current Version is 1
ExampleModel example = exampleDataAccessObject.findById(1L);
example.setSomeData("test");
//get the second object what will cause the exception
//current version is 1
ExampleModel exampleOldObject = exampleDataAccessObject.findById(1L);
// returnValue with the version number 2
ExampleModel returnValue = facade.persistOrUpdateUser(example);
//try to save the object with the version 1
//throws the OptimisticLockException
facade.persistOrUpdateUser(exampleOldObject);
}
}
I am testing JdbcSpitterDao#getSpitterByid() and am mocking jdbcTemplate.queryForObject()
public class JdbcSpitterDao extends JdbcDaoSupport implements SpitterDao {
...
public Spitter getSpitterById(long id,
ParameterizedRowMapper<Spitter> parameterizedRowMapper) {
JdbcTemplate jdbcTemplate = getJdbcTemplate();
return jdbcTemplate.queryForObject(SQL_SELECT_SPITTER,
parameterizedRowMapper, id);
}
...
}
My test object contains the following
ParameterizedRowMapper<Spitter> parameterizedRowMapper = new ParameterizedRowMapper<Spitter>() {
public Spitter mapRow(ResultSet rs, int rowNum) throws SQLException {
Spitter Spitter = new Spitter();
spitter.setId(rs.getLong(1));
spitter.setUsername(rs.getString(2));
spitter.setPassword(rs.getString(3));
spitter.setFullName(rs.getString(4));
spitter.setEmail(rs.getString(5));
return spitter;
}
};
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
JdbcSpitterDao jdbcSpitterDao = new JdbcSpitterDao();
jdbcSpitterDao.setJdbcTemplate(jdbcTemplate);
// Don't connect to the database. Mock JdbcTemplate class
when(
jdbcTemplate.queryForObject(
JdbcSpitterDao.SQL_SELECT_SPITTER,
parameterizedRowMapper, 1)).thenReturn(new Spitter(1, "rajkumarm", "rajmukarm",
"Rajkumar Masaniayan", "rajkumarm#gmail.com"));
// Actual test
Spitter actualSpitter = jdbcSpitterDao.getSpitterById(1, parameterizedRowMapper);
But when().thenReturn() idiom is not getting invoked. The control is flowing to actual jdbcTemplate. Looks like there is a problem with
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
or
jdbcSpitterDao.setJdbcTemplate(jdbcTemplate);
Would you please let me know what is going wrong?
There is this suggestion not to mock the types you don't own. In general it is a good suggestion and here it really makes sense not to unit test that dao at all. Why not just integration test sth which operates on DB and does no additional logic?