I am trying to implement a sharded Hibernate logic. All Databases have same table called MyTable which is mapped to MyClass through Hibernate POJO.
public class SessionFactoryList {
List<SessionFactory> factories;
int minShard;
int maxShard;
// getters and setters here.
}
In my Dao implementation, I have a method getAll which is following -
public class MyClassDao {
#Autowired // through Spring
private SessionFactoryList list;
List<MyClass> getAll() {
List<MyClass> outputList = new ArrayList<>();
for(SessionFactory s : list.getFactories()) {
Criteria c = s.getCurrentSession.createCriteria(MyClass.class);
outputList.addAll(c.list());
}
return outputList;
}
Here is my test for the corresponding getAll implementation -
public class MyClassTest {
#Autowired
SessionFactoryList list;
#Autowired
MyClassDao myClassDao;
#Test
void getAllTest() {
Session session1 = list.getFactories.get(0).getCurrentSession();
session1.beginTransaction();
session1.save(new MyClass(// some parameters here));
Session session2 = list.getFactories.get(1).getCurrentSession();
session2.beginTransaction();
session2.save(new MyClass(// some parameters here));
//Set up done.
assert myClassDao.getAll().size() == 2
}
}
I am using HSQL in-memory database for the test cases.
I have verified that DB connections are correctly setup, but the Assert statement is failing.
'getAll' method of MyClassDao is returning 3 rows. MyClass object inserted in SessionFactory1's session is getting duplicated.
Is there anything I am missing out here?
I found it. The 2 sessionFactory configurations which I used for the test had same Database URL. Hence the same database was queried twice which caused the duplicates.
Related
I have two methods, the first returning a list of elements and the second returning a single element:
List<User> getUsersFromExternalSystem(List<Integer> userIds);
User getUserFromExternalSystem(Integer userId);
I would like Spring to cache the results of these two methods, so that when the list of elements method (getUsersFromExternalSystem()) is called it caches the results for the provided ids (userIds) and when the single element method (getUserFromExternalSystem()) is called with the id previously provided to the list of elements method it uses the cache.
I can simply apply #Cacheable to these methods, then (if I understand correctly) when I call:
getUsersFromExternalSystem(Arrays.asList(1, 2))
the results will be cached but when I call
getUserFromExternalSystem(1);
the cache will not be used. How this be done in Spring?
You can use following approach. Only first method getUser(Integer id) is cacheable, and second method just combines the results of getUser invocations.
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class UserService {
private final UserService self;
#Autowired
public UserService(UserService userService) {
self = userService;
}
#Cacheable(cacheNames = "users", key = "id") // assuming that you've already initialized Cache named "users"
public User getUser(Integer id) {
return new User(); // ... or call to some DataSource
}
public List<User> getUsers(List<Integer> ids) {
List<User> list = new ArrayList<>();
for (Integer id : ids) {
list.add(self.getUser(id));
}
return list;
}
}
The trick with injecting a bean into himself and calling
self.getUser(id) instead of this.getUser(id)
is required because #Cacheable will be actually invoked only when used on a Spring proxied bean, and this is not a proxy. More details here Transactions, Caching and AOP: understanding proxy usage in Spring
I created a SpringBoot test:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource(locations = "classpath:application-dev.properties")
#Transactional
public class ContactTests2 {
private Logger log = LogManager.getLogger();
#PersistenceContext
private EntityManager entityManager;
#Autowired
private ContactRepository customerRepository;
#Autowired
private StoreRepository storeRepository;
#Autowired
private NoteService noteService;
#Autowired
private Validator validator;
private Store store;
#Before
#WithMockUser(roles = "ADMIN")
public void setup() {
log.debug("Stores {}", storeRepository.count());
store = createStore();
storeRepository.save(store);
}
#Test
#WithMockUser(roles = "ADMIN")
public void saveWithNote() {
Contact customer = new Contact();
customer.setPersonType(PersonType.NATURAL_PERSON);
customer.setFirstName("Daniele");
customer.setLastName("Rossi");
customer.setGender(Gender.MALE);
customer.setBillingCountry(Locale.ITALY.getCountry());
customer.setShippingCountry(Locale.ITALY.getCountry());
customer.setStore(store);
Note note = new Note();
note.setGenre(NoteGenre.GENERIC);
note.setOperationType(AuditType.NOTE);
note.setText("note");
customer = customerRepository.save(customer);
noteService.addNote(note, customer);
}
#Test
#WithMockUser(roles = "ADMIN")
public void save() {
Contact customer = new Contact();
customer.setPersonType(PersonType.NATURAL_PERSON);
customer.setFirstName("Daniele");
customer.setLastName("Rossi");
customer.setGender(Gender.MALE);
customer.setBillingCountry(Locale.ITALY.getCountry());
customer.setShippingCountry(Locale.ITALY.getCountry());
customer.setStore(store);
customerRepository.save(customer);
assertEquals(customer, customerRepository.findById(customer.getId()).get());
}
// ====================================================
//
// UTILITY METHODS
//
// ====================================================
private Store createStore() {
Store store = new Store();
store.setName("Padova");
store.setCode("PD");
store.setCountry("IT");
return store;
}
}
this is the note service:
#Service
#Transactional
#PreAuthorize("isAuthenticated()")
public class NoteService {
#PersistenceContext
private EntityManager entityManager;
#Autowired
private NoteRepository noteRepository;
/**
* Add a note to a specific object (parent).
*
* #param note
* #param parent
* #return the added note
*/
public Note addNote(Note note, Persistable<Long> parent) {
// ****************************************************
// VALIDATION CHECKS
// ****************************************************
Assert.notNull(note, InternalException.class, ExceptionCode.INTERNAL_ERROR);
Assert.notNull(parent, InternalException.class, ExceptionCode.INTERNAL_ERROR);
// ****************************************************
// END VALIDATION CHECKS
// ****************************************************
note.setParentId(parent.getId());
note.setParentType(parent.getClass().getSimpleName());
note.setRemoteAddress(NetworkUtils.getRemoteIpFromCurrentContext());
note = noteRepository.save(note);
return note;
}
}
I'm using Hibernate and Mysql 5.7. The problem is that the test called saveWithNote(). When I run this test, following tests fails because the setup() method throw a duplicated exception. It seems the previous test is not rolledback.
This is what happens:
Removing the line noteService.addNote(note, customer); everything works like a charm.
What am I doing wrong? Why test isolation is not preserved?
This is because you are using a real data store as the dependency.
When running saveWithNote(), the customer entry is persisted in database. It is not removed in your test setup, so the when you run save(), you bump into a duplicate database entry.
Solution 1:
Use teardown() method to remove database entries you created during the test.
Example:
#After
#WithMockUser(roles = "ADMIN")
public void teardown() {
// delete the customer entry here
}
Reference: https://examples.javacodegeeks.com/core-java/junit/junit-setup-teardown-example/
Solution 2: Every time you run setup(), wipe the database tables clean.
Example:
#Before
#WithMockUser(roles = "ADMIN")
public void setup() {
// wipe your database tables to make them empty
}
Both solution 1 and 2 should be done with test database only. You DON'T want to clean up production DB.
Solution 3 (recommended):
Use mocked repositories and mock injection (instead of autowiring repositories with real implementation).
Sample/ Reference: https://stackoverflow.com/a/36004293/5849844
Most likely your table is using MyISAM storage engine which does not support transactions (as per Table 15.2 MyISAM Storage Engine Features docs).
Redefine the table using InnoDB storage engine. Take a look at 14.8.1.1 Creating InnoDB Tables docs, it should be on by default but you can check it with:
SELECT ##default_storage_engine;
I use following tecnologies:
TestNG(6.9.10)
Spring(4.3.2.RELEASE)
Hibernate(5.1.0.Final)
Java 8
I test some code with functionality by integration tests and i need to check the entity for correct save/update/delete or any other changes. There are sessionFactory configuration in my .xml :
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"
p:dataSource-ref="dataSource" p:hibernateProperties="jdbcProperties">
<property name="packagesToScan" value="my.package"/>
</bean>
and test class example:
#ContextConfiguration(locations = {"classpath:/applicationContext-test.xml",
"classpath:/applicationContext-dao.xml",
"classpath:/applicationContext-orm.xml"})
public class AccountServiceTest extends AbstractTransactionalTestNGSpringContextTests {
#Autowired
private SomeService someService;
#Autowired
private SessionFactory sessionFactory;
#Test
public void updateEntity() {
//given
Long entityId = 1L;
SomeClass expected = someService.get(entityId);
String newPropertyValue = "new value";
//when
someService.changeEntity(entity, newPropertyValue);
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
//then
expected = someService.get(entityId);
Assert.assertEquals(expected.getChangedProperty() , newPropertyValue);
}
service method:
#Transactional
#Override
public int changeEntity(entity, newPropertyValue) {
return dao().executeNamedQuery(REFRESH_ACCESS_TIME_QUERY,
CollectionUtils.arrayToMap("id", entity.getId(), "myColumn", newPropertyValue));
}
dao:
#Override
public int executeNamedQuery(final String query, final Map<String, Object> parameters) {
Query queryObject = sessionFactory.getCurrentSession().getNamedQuery(query);
if (parameters != null) {
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
NamedQueryUtils.applyNamedParameterToQuery(queryObject, entry.getKey(), entry.getValue());
}
}
return queryObject.executeUpdate();
}
But my entity property didn't change after flush()
as described here, change #Autowire SessionFactory with #PersistenceContext EntityManager , i should use EntityManager to flush() - but i can't do this - i can't transform sessionFactory to EntityManager, and i don't need in creation of EntityManager for my application - because i need to change my .xml config file and others.
Is there are any another solutions of this problem?
Your code is actually working as expected.
Your test method is transactional and thus your Session is alive during the whole execution of the test method. The Session is also the 1st level cache for hibernate and when loading an entity from the database it is put into the session.
So the line SomeClass expected = someService.get(entityId); will load the entity from the database and with it also put it in the Session.
Now this line expected = someService.get(entityId); first checks (well actually the dao method underneath) checks if the entity of the requested type with the id is already present in the Session if so it simply returns it. It will not query the database!.
The main problem is that you are using hibernate in a wrong way, you are basically bypassing hibernate with the way you are updating your database. You should update your entity and persist it. You should not write queries to update the database!
Annotated test method
#Test
public void updateEntity() {
//given
Long entityId = 1L;
SomeClass expected = someService.get(entityId); // load from db and put in Sesion
String newPropertyValue = "new value";
//when
someService.changeEntity(entity, newPropertyValue); // update directly in database bypass Session and entity
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
//then
expected = someService.get(entityId); // return entity from Session
Assert.assertEquals(expected.getChangedProperty() , newPropertyValue);
}
To only fix the test add a call to clear() after the flush().
sessionFactory.getCurrentSession().clear();
However what you actually should do is stop writing code like that and use Hibernate and persistent entities in the correct way.
#Test
public void updateEntity() {
//given
Long entityId = 1L;
String newPropertyValue = "new value";
SomeClass expected = someService.get(entityId);
expected.setMyColumn(newPropertyValue);
//when
someService.changeEntity(entity);
sessionFactory.getCurrentSession().flush();
// now you should use a SQL query to verify the state in the DB.
Map<String, Object> dbValues = getJdbcTemplate().queryForMap("select * from someClass where id=?", entityId);
//then
Assert.assertEquals(dbValues.get("myColumn"), newPropertyValue);
}
Your dao method should look something like this.
public void changeEntity(SomeClass entity) {
sessionFactory.getCurrentSession().saveOrUpdate(entity);
}
There is an Hibernate domain object that have 'not null' field which have annotation #Column(nullable=false), a DAO class method which is saving this object in DB.
I am mocking the create DAO call using PowerMockito, mock call is working fine but if i am passing null for the field, mock test is not throwing error that field is null.
Below is the code, tools/techs (java, spring, hibernate, sqlserver, junits, powermockit). Few code changes omitted related to spring context and hibernate session configurations.
public class Entity{
private String id;
#Column(nullable=false)
private String field;
//setters and getters goes here
}
public class HibernateDAO{
private SessionFactory sessionFactory;
public void create(Entity entity){
getSession().save(entity);
}
public void setSessionFactory(SessionFactory sf){
sessionFactory = sf;
}
}
public class HibernateDAOTest{
HibernateDAO hibernateDAO = new HibernateDAO();
public SessionFactory mockedSessionFactory;
public Session mockedSession;
public Query query;
public SQLQuery sqlQuery;
public Transaction mockedTransaction;
#Before
public void setup() {
mockedSessionFactory = PowerMockito.mock(SessionFactory.class);
mockedSession = PowerMockito.mock(Session.class);
mockedTransaction = PowerMockito.mock(Transaction.class);
query = PowerMockito.mock(Query.class);
sqlQuery = PowerMockito.mock(SQLQuery.class);
PowerMockito.when(mockedSessionFactory.openSession()).thenReturn(mockedSession);
PowerMockito.when(mockedSessionFactory.getCurrentSession()).thenReturn(mockedSession);
PowerMockito.when(mockedSession.beginTransaction()).thenReturn(mockedTransaction);
}
#Test
public void testCreate(){
Entity entityToSave = new Entity();
entityToSave.setId("123");
entityToSave.setField(null);
hibernateDAO.setSessionFactory(mockedSessionFactory);
hibernateDAO.create(entityToSave);//this call should throw error that "field" is null but its not.
}
}
Actually the validation (not-nullability check) is not done in your DAO. You do declare your field as not-null, but then your create(Entity entity) method calls:
getSession().save(entity);
and that is all it does. Now the validation would happen within the saving via HibernateSession, but all those classed are mocked. So they will not perform any validation.
Generally it is a good rule, that each time you accidentally mock the very same thing you tested, you step back and re-evaluate:
Did I write that code? Why do I want to test it?
In this case probably the answer is, that you should trust Hibernate taking care of things. And if you want to test your model definition, you need to setup a full Hibernate context and perform a real test with Hibernate not being mocked.
I have two services, like this (simplified code):
#Service
public class OuterService {
#Autowired
InnerService innerService;
#Transactional
public void doSomething() {
List<SomeEntity> list = entityRepo.findByWhatever(...);
for(SomeEntity listElement : list) {
innerService.processEntity(listElement);
}
}
}
#Service
public class InnerService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void processEntity(Entity entity) {
// ...
StatusElement status = new StatusElement(...);
statusElementRepo.save(status);
}
}
The constructed StatusElement is now inserted by exiting InnerService.processEntity() and inserted again by exiting OuterService.doSomething().
If I change the #Transactional annotation of OuterService.doSomething() to #Transactional(readOnly = true), it is inserted just once.
Is it a problem with MySql (because it may not support nested transactions), do I need a special transaction manager, or is there something wrong with my code? TIA!
I solved it by using programmatically transactions using the PlatformTransactionManager.
see: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html#transaction-programmatic-ptm