Spring's #Transactional is not working after flushing the entity in db - spring

I have below class where I used #Transactional, I want when any error occurs in any of the method then transaction should be rolled back.
#Service
#Slf4j
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class CspConfigurationDeployer implements CspConfigurationDeployerInterface {
private CSPConfigDBService cspConfigDBService;
#Override
#Synchronized
#Transactional(rollbackFor = Exception.class)
public Boolean deploy(Map<String, InputStream> inputStreams) throws Exception {
Boolean success = true;
ObjectMapper mapper = new ObjectMapper();
CarrierinfoEntity carrierinfo = null;
CarrierversionEntity carrierversionEntity = null;
try {
if (onboardingcarrierdetailsEntity != null && !ObjectUtils.isEmpty(carrierinfo)) {
carrierversionEntity = cspConfigDBService.getCarrierVersion(carrierinfo.getId(),
onboardingcarrierdetailsEntity.getCarrierversion().getMajorversion());
}
if (ObjectUtils.isEmpty(carrierversionEntity)) {
if (!cspConfigDBService.doesRequestFormExist(carrierinfo.getId(), availablecarrierdetailsEntity.getVersion())) {
availablecarrierdetailsEntity.setCarrierinfo(carrierinfo);
cspConfigDBService.updateEntity(availablecarrierdetailsEntity); // Add request form data
}
}
}
if (onboardingcarrierdetailsEntity != null && ObjectUtils.isEmpty(carrierversionEntity)) {
CarrierversionEntity carrierVersion = onboardingcarrierdetailsEntity.getCarrierversion();
List<CspparametersEntity> cspparametersEntities = carrierVersion.getCarrierParameters();
CarrierversionEntity updatedCarrierVersion = updateCarrierVersion(carrierVersion, carrierinfo.getId());
updateCspParameter(updatedCarrierVersion, carrierinfo.getId(), cspparametersEntities);
List<EnvSpecificParametersEntity> envSpecificParametersEntities = onboardingcarrierdetailsEntity.getEnvSpecificParameters();
OnboardingcarrierdetailsEntity updatedOnboradingDetails = updateOnboardingDetails(onboardingcarrierdetailsEntity,
carrierinfo, updatedCarrierVersion);
updateEnvSpecificParams(updatedOnboradingDetails, envSpecificParametersEntities);
}
} catch (Exception ex) {
log.error("CspConfiguration deployment encountered exception: " + ex.getMessage(), ex);
throw ex;
}
#Repository
#Slf4j
#AllArgsConstructor(onConstructor = #__(#Autowired))
#Transactional(rollbackFor = Exception.class)
public class CSPConfigDBServiceImpl implements CSPConfigDBService {
private final SessionFactory cspConfigurationSessionFactory;
private Session getCspConfigurationSessionFactory() {
return cspConfigurationSessionFactory.getCurrentSession();
}
#Override
public void updateEntity(Object entity) {
Session session = getCspConfigurationSessionFactory();
session.merge(entity);
session.flush();
}
}
when I don't use flush with merge operation then rollback is working but I need the id stored in db immediately for further use so I need to flush it.

Related

PowerMockito is calling the real method instead of the moked one and gives NullPointException

I have a private method and calls another private method in its body. I want to mock the behaviour of the second one but when I use PowerMock, it calls the actuall method instead of the mocked one and gives NPE.
First private method
private UserTypes getUserType(String msisdn, String uuid) throws BaseException {
logger.info("{}| getUserType method started",msisdn);
UserTypes userType;
Relationship relationship = relationshipService.getRelationshipByChildMsisdn(msisdn);
if (relationship !=null){
if (relationship.getMasterMsisdn() != null){
userType = UserTypes.CHILD;
}else {
userType = UserTypes.CHILD_WITHOUT_MASTER;
}
} else if ( relationshipService.isMasterExists(msisdn)) {
userType = UserTypes.MASTER;
} else {
QuerySubscribedGroupStatus subscribedGroupStatus = querySubscribedGroupAPICallService.querySubscribedGroupToGetStatus(uuid,msisdn);
if (subscribedGroupStatus.equals(QuerySubscribedGroupStatus.NEW_USER)){
if (checkForCallBlock(msisdn)) userType = UserTypes.NON_ELIGIBLE_USER;
else userType = UserTypes.FRESH_USER;
} else if (subscribedGroupStatus.equals(QuerySubscribedGroupStatus.BELONGS_TO_ANOTHER_CUG)) {
userType = UserTypes.NON_ELIGIBLE_USER;
} else if (subscribedGroupStatus.equals(QuerySubscribedGroupStatus.SUBSCRIBER_NOT_FOUND)) {
BaseResponseData data = new BaseResponseData(ReasonCodes.SUB_NOT_IN_OCS.getReasonCode(), ReasonCodes.SUB_NOT_IN_OCS.getReasonDescription());
throw new BaseException(ResultCode.REQUEST_FAILED.getCode(), ResultCode.REQUEST_FAILED.getDescription(), HttpStatus.OK,data);
} else {
BaseResponseData data = new BaseResponseData(ReasonCodes.ERROR_IN_QUERY_SUB_GROUP.getReasonCode(), ReasonCodes.ERROR_IN_QUERY_SUB_GROUP.getReasonDescription());
throw new BaseException(ResultCode.EXTERNAL_API_CALL_FAILURE.getCode(), ResultCode.EXTERNAL_API_CALL_FAILURE.getDescription(), HttpStatus.INTERNAL_SERVER_ERROR,data);
}
}
logger.info("{}| getUserType method ended | Returned userType: {}",msisdn,userType);
return userType;
}
Second private method.
private boolean checkForCallBlock(String msisdn) throws BaseException {
logger.info("{}| checkForCallBlock method started",msisdn);
boolean callBlockActivated = false;
try{
vasMgtAPICallService.isRemoteVASPackageActivated(msisdn,msisdn,callBlockPackage);
}catch (BaseException ex){
if (ex.getData().getReasonCode() == 30010){
logger.info("{}|Call block activated subscriber detected",msisdn);
return true;
}else {
throw new BaseException (ex.getResultCode(),ex.getResultDescription(),ex.getStatus(),ex.getData());
}
}
return callBlockActivated;
}
This is what I have tried.
#RunWith(PowerMockRunner.class)
#PrepareForTest(StatusMainService.class)
class StatusMainServiceTest {
#Spy
#InjectMocks
StatusMainService statusMainService;
#Mock
QuerySubscribedGroupAPICallService querySubscribedGroupAPICallService;
#Mock
RelationshipService relationshipService;
#Mock
VASMgtAPICallService vasMgtAPICallService;
#Mock
PcrfPackageAPICallService pcrfPackageAPICallService;
#Mock
RestrictionPacksService restrictionPacksService;
#Mock
CcbsNumberBasicInfoApiCallService ccbsNumberBasicInfoApiCallService;
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(statusMainService,"callBlockPackage", "I_CB");
}
#Test
void getStatusData() {
}
#Test
void processDataRestriction() {
}
#Test
void getUserType() throws Exception {
Relationship relationship1 = new Relationship();
relationship1.setMasterMsisdn("779021671");
doReturn(relationship1).when(relationshipService).getRelationshipByChildMsisdn("761408091");
doReturn(new Relationship()).when(relationshipService).getRelationshipByChildMsisdn("761408092");
doReturn(null).when(relationshipService).getRelationshipByChildMsisdn("761408093");
doReturn(true).when(relationshipService).isMasterExists("761408093");
doReturn(null).when(relationshipService).getRelationshipByChildMsisdn("761408094");
doReturn(false).when(relationshipService).isMasterExists("761408094");
doReturn(QuerySubscribedGroupStatus.NEW_USER).when(querySubscribedGroupAPICallService).querySubscribedGroupToGetStatus("1234567890", "761408094");
StatusMainService statusMainServiceMock = PowerMockito.spy(new StatusMainService());
PowerMockito.doReturn(true).when(statusMainServiceMock,"checkForCallBlock", "761408094");
UserTypes userType1 = Whitebox.invokeMethod(statusMainService, "getUserType", "761408091", "1234567890");
UserTypes userType2 = Whitebox.invokeMethod(statusMainService, "getUserType", "761408092", "1234567890");
UserTypes userType3 = Whitebox.invokeMethod(statusMainService, "getUserType", "761408093", "1234567890");
UserTypes userType4 = Whitebox.invokeMethod(statusMainService, "getUserType", "761408094", "1234567890");
assertEquals(UserTypes.CHILD, userType1);
assertEquals(UserTypes.CHILD_WITHOUT_MASTER, userType2);
assertEquals(UserTypes.MASTER, userType3);
assertEquals(UserTypes.NON_ELIGIBLE_USER, userType4);
}
This is where I mocked the second private method.
PowerMockito.doReturn(true).when(statusMainServiceMock,"checkForCallBlock", "761408094")
How I can solve this issue?

How can i use #autowire in runnable spring boot

I have few MongoTemplate and Repos and i need to call them using #Autowire in my runnable class that is being executed by exceutor class using multi threading, now the problem is that when i run the application my AutoWire for mongoTempelate and Repos returns null pointer exception.
Executor class:
#Component
public class MessageConsumer implements ConsumerSeekAware {
#Autowired
AlarmDataRepository alarmDataRepository;
int assignableCores = ((Runtime.getRuntime().availableProcessors()));
ExecutorService executor = Executors.newFixedThreadPool(
assignableCores > 1 ? assignableCores : 1
);
int counter = 0;
List<String> uniqueRecords = new ArrayList<String>();
#KafkaListener(topics = "teltonikaTest", groupId = "xyz")
public void processMessages(#Payload List<String> payload, #Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions, #Header(KafkaHeaders.OFFSET) List<Long> offsets) throws UnsupportedEncodingException, DecodeException {
System.out.println("assignable resources are: " + assignableCores);
log.info("Batch Size is: {}", payload.size());
if(counter==0){
log.info("Teletonica Packets Received!");
}
for (int i = 0; i < payload.size(); i++) {
log.info("processing message='{}' with partition off-set='{}'", payload.get(i), partitions.get(i) + " _" + offsets.get(i));
}
uniqueRecords = payload.stream().distinct().collect(Collectors.toList());
Runnable worker = new TeltonikaWorkerThread(uniqueRecords);
executor.execute(worker);
counter++;
}
}
public class TeltonikaWorkerThread implements Runnable{
List<String> records;
List<CurrentDevice> currentDevices = new ArrayList<>();
#Autowired
CurrentDeviceRepository currentDeviceRepository;
#Autowired
MongoTemplate mongoTemplate;
public TeltonikaWorkerThread(List<String> records) {
this.records = records;
}
public void run() {
try {
processMessage();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (DecodeException e) {
e.printStackTrace();
}
}
public void processMessage() throws UnsupportedEncodingException,DecodeException {
for(Object record : records){
if(record!="0"){
try{
int IMEILength = record.toString().indexOf("FF");
String IMEI = record.toString().substring(0,IMEILength);
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
}
If I understand correctly, your problem is about multiple beans and Spring doesn't know which one should be injected. There are several options here.
For example, you can use #Qualifier annotation based on the bean name or #Primary annotation.
If your problem is something else, please add an example to your question.

In spring accessing service class in controller and storing response in embedded class

I have dao class is below
#Override
public List<PersonDTO> getAllEmployees(long companyId) {
Query query = getCurrentSession().getNamedQuery(User.class.getName() + ".getAllEmployee");
query.setParameter("companyId", companyId);
query.setResultTransformer(Transformers.aliasToBean(PersonDTO.class));
return query.list();
}
I have services class is below
public List<PersonDTO> getAllEmployees(long companyId) {
try {
return userDAO.getAllEmployees(companyId);
} catch (Exception e) {
throw new ApplicationException(e);
}
}
I have Embedded class
public class EntityListResponce implements Serializable {
private static final long serialVersionUID = 1L;
private List<PersonDTO> personDTO;
public List<PersonDTO> getPersonDTO() {
return PersonDTO;
}
public void setPersonDTO(List<PersonDTO> employeesList) {
this.PersonDTO = employeesList;
}
}
I written a controller like below.
#RequestMapping("/getAllEntitys.htm")
public void getAllEntities(HttpServletRequest request, HttpServletResponse response) {
try {
ZSession zSession = getZSession(request);
Long companyId = zSession.getCompanyId();
EntityListResponce entityListResponce = new EntityListResponce();
List<PersonDTO> employeesList = personService.getAllEmployees(companyId);
WebUtils.writeJSONResponse(response,
getSerializer(request, EXCLUDE_FILES).deepSerialize(entityListResponce));
} catch (Exception e) {
writeException(request, response, e);
}
}
}
Is it proper way to collect all the employeesList like this
List<PersonDTO> employeesList = personService.getAllEmployees(companyId);
I am getting not getting all the employeesList in response.

how to add entity listener programmable in Hibernate JPA

I use spring, hibernate, jpa2.1.
as follow:
#Entity
#EntityListeners(DemoListener.class)
public class Demo {
#Id
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
public class DemoListener {
#PersistenceContext
private EntityManager entityManager;
#PrePersist
public void prePersist(Demo demo){
}
}
the example works well, when I want to add more listener, I must modify the Demo entity, but the Demo is in other jar,I don't want to use the XML configuration, is there some way like this:
...addListener(Demo.class, new DemoListener());
...addListener(Demo.class, new OtherDemoListener());
base on hibernate-orm docs and hibernate-tutorials:
/**
* #param <T> one of {#link EventType#baseListenerInterface()}
* #see org.hibernate.event.service.spi.EventListenerRegistry
*/
public interface JpaEventListenerRegistry<T> {
/**
* add listener for entity class
*
* #param entityClass can't be null
* #param listener can't be null
*/
void addListener(Class<?> entityClass, T listener);
}
public class JpaEventListenerRegistryImpl implements JpaEventListenerRegistry<Object> {
private Logger logger = LoggerFactory.getLogger(getClass());
private EventListenerRegistry eventListenerRegistry;
private Map<EventType, JpaEventListenerRegistry> listeners = new HashMap<EventType, JpaEventListenerRegistry>();
public JpaEventListenerRegistryImpl(EventListenerRegistry eventListenerRegistry) {
this.eventListenerRegistry = eventListenerRegistry;
initDefault();
}
private void initDefault() {
listeners.put(EventType.PRE_INSERT, new DomainPreInsertEventListener());
listeners.put(EventType.POST_INSERT, new DomainPostInsertEventListener());
for (Map.Entry<EventType, JpaEventListenerRegistry> entry : listeners.entrySet()) {
eventListenerRegistry.appendListeners(entry.getKey(), entry.getValue());
}
}
#SuppressWarnings("unchecked")
public void addListener(Class<?> entityClass, Object listener) {
logger.info("add listener {} for entity {}", listener, entityClass.getName());
for (EventType eventType : EventType.values()) {
Class<?> listenerInterface = eventType.baseListenerInterface();
if (listenerInterface.isAssignableFrom(listener.getClass())) {
JpaEventListenerRegistry registry = listeners.get(eventType);
if (registry == null) {
logger.warn("the event type {} for listener {} is not supported", eventType, listener);
} else {
registry.addListener(entityClass, listener);
}
}
}
}
public static class Abstract<T> implements JpaEventListenerRegistry<T> {
private Logger logger = LoggerFactory.getLogger(getClass());
private Map<Class<?>, List<T>> listeners = new HashMap<Class<?>, List<T>>();
public void addListener(Class<?> entityClass, T listener) {
logger.info("add listener {} for entity {}", listener, entityClass.getName());
List<T> listeners = this.listeners.get(entityClass);
if (listeners == null) {
listeners = new ArrayList<T>();
this.listeners.put(entityClass, listeners);
}
listeners.add(listener);
}
List<T> findListener(Class<?> entityClass) {
for (Map.Entry<Class<?>, List<T>> entry : listeners.entrySet()) {
if (entry.getKey().isAssignableFrom(entityClass)) {
return entry.getValue();
}
}
return null;
}
}
public static class DomainPreInsertEventListener extends Abstract<PreInsertEventListener> implements PreInsertEventListener {
public boolean onPreInsert(PreInsertEvent event) {
return onPreInsert(event, findListener(event.getEntity().getClass()));
}
private boolean onPreInsert(PreInsertEvent event, List<PreInsertEventListener> listeners) {
if (listeners == null) return false;
for (PreInsertEventListener listener : listeners) {
if (listener.onPreInsert(event)) return true;
}
return false;
}
}
public static class DomainPostInsertEventListener extends Abstract<PostInsertEventListener> implements PostInsertEventListener {
public void onPostInsert(PostInsertEvent event) {
onPostInsert(event, findListener(event.getEntity().getClass()));
}
private void onPostInsert(PostInsertEvent event, List<PostInsertEventListener> listeners) {
if (listeners == null) return;
for (PostInsertEventListener listener : listeners) {
listener.onPostInsert(event);
}
}
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
}
public class EntityManagerIllustrationTest extends TestCase {
private EntityManagerFactory entityManagerFactory;
#Override
protected void setUp() throws Exception {
// like discussed with regards to SessionFactory, an EntityManagerFactory is set up once for an application
// IMPORTANT: notice how the name here matches the name we gave the persistence-unit in persistence.xml!
entityManagerFactory = Persistence.createEntityManagerFactory("org.hibernate.tutorial.jpa");
SessionFactoryImplementor sessionFactory = entityManagerFactory.unwrap(SessionFactoryImplementor.class);
EventListenerRegistry eventListenerRegistry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
JpaEventListenerRegistryImpl jpaEventListenerRegistry = new JpaEventListenerRegistryImpl(eventListenerRegistry);
jpaEventListenerRegistry.addListener(EventListener.class, new JpaEventListener());
}
private static class JpaEventListener implements PreInsertEventListener, PostInsertEventListener {
public boolean onPreInsert(PreInsertEvent event) {
Event entity = (Event) event.getEntity();
System.out.println("onPreInsert:" + entity);
return false;
}
public void onPostInsert(PostInsertEvent event) {
Event entity = (Event) event.getEntity();
System.out.println("onPostInsert:" + entity);
}
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
#Override
protected void tearDown() throws Exception {
entityManagerFactory.close();
}
public void testBasicUsage() {
// create a couple of events...
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(new Event("Our very first event!", new Date()));
// entityManager.persist(new Event("A follow up event", new Date()));
entityManager.getTransaction().commit();
entityManager.close();
// now lets pull events from the database and list them
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
List<Event> result = entityManager.createQuery("from Event", Event.class).getResultList();
for (Event event : result) {
System.out.println("Event (" + event.getDate() + ") : " + event.getTitle());
}
entityManager.getTransaction().commit();
entityManager.close();
}
}

transactional unit testing with ObjectifyService - no rollback happening

We are trying to use google cloud datastore in our project and trying to use objectify as the ORM since google recommends it. I have carefully used and tried everything i could read about and think of but somehow the transactions don't seem to work. Following is my code and setup.
#RunWith(SpringRunner.class)
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ContextConfiguration(classes = { CoreTestConfiguration.class })
public class TestObjectifyTransactionAspect {
private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
// Our tests assume strong consistency
new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy(),
new LocalMemcacheServiceTestConfig(), new LocalTaskQueueTestConfig());
private Closeable closeableSession;
#Autowired
private DummyService dummyService;
#BeforeClass
public static void setUpBeforeClass() {
// Reset the Factory so that all translators work properly.
ObjectifyService.setFactory(new ObjectifyFactory());
}
/**
* #throws java.lang.Exception
*/
#Before
public void setUp() throws Exception {
System.setProperty("DATASTORE_EMULATOR_HOST", "localhost:8081");
ObjectifyService.register(UserEntity.class);
this.closeableSession = ObjectifyService.begin();
this.helper.setUp();
}
/**
* #throws java.lang.Exception
*/
#After
public void tearDown() throws Exception {
AsyncCacheFilter.complete();
this.closeableSession.close();
this.helper.tearDown();
}
#Test
public void testTransactionMutationRollback() {
// save initial list of users
List<UserEntity> users = new ArrayList<UserEntity>();
for (int i = 1; i <= 10; i++) {
UserEntity user = new UserEntity();
user.setAge(i);
user.setUsername("username_" + i);
users.add(user);
}
ObjectifyService.ofy().save().entities(users).now();
try {
dummyService.mutateDataWithException("username_1", 6L);
} catch (Exception e) {
e.printStackTrace();
}
List<UserEntity> users2 = this.dummyService.findAllUsers();
Assert.assertEquals("Size mismatch on rollback", users2.size(), 10);
boolean foundUserIdSix = false;
for (UserEntity userEntity : users2) {
if (userEntity.getUserId() == 1) {
Assert.assertEquals("Username update failed in transactional context rollback.", "username_1",
userEntity.getUsername());
}
if (userEntity.getUserId() == 6) {
foundUserIdSix = true;
}
}
if (!foundUserIdSix) {
Assert.fail("Deleted user with userId 6 but it is not rolledback.");
}
}
}
Since I am using spring, idea is to use an aspect with a custom annotation to weave objectify.transact around the spring service beans methods that are calling my daos.
But somehow the update due to ObjectifyService.ofy().save().entities(users).now(); is not gettign rollbacked though the exception throws causes Objectify to run its rollback code. I tried printing the ObjectifyImpl instance hashcodes and they are all same but still its not rollbacking.
Can someone help me understand what am i doing wrong? Havent tried the actual web based setup yet...if it cant pass transnational test cases there is no point in actual transaction usage in a web request scenario.
Update: Adding aspect, services, dao as well to make a complete picture. The code uses spring boot.
DAO class. Note i am not using any transactions here because as per code of com.googlecode.objectify.impl.TransactorNo.transactOnce(ObjectifyImpl<O>, Work<R>) a transnational ObjectifyImpl is flushed and committed in this method which i don't want. I want commit to happen once and rest all to join in on that transaction. Basically this is the wrong code in com.googlecode.objectify.impl.TransactorNo ..... i will try to explain my understanding a later in the question.
#Component
public class DummyDaoImpl implements DummyDao {
#Override
public List<UserEntity> loadAll() {
Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
return query.list();
}
#Override
public List<UserEntity> findByUserId(Long userId) {
Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
//query = query.filterKey(Key.create(UserEntity.class, userId));
return query.list();
}
#Override
public List<UserEntity> findByUsername(String username) {
return ObjectifyService.ofy().transactionless().load().type(UserEntity.class).filter("username", username).list();
}
#Override
public void update(UserEntity userEntity) {
ObjectifyService.ofy().save().entity(userEntity);
}
#Override
public void update(Iterable<UserEntity> userEntities) {
ObjectifyService.ofy().save().entities(userEntities);
}
#Override
public void delete(Long userId) {
ObjectifyService.ofy().delete().key(Key.create(UserEntity.class, userId));
}
}
Below is the Service class
#Service
public class DummyServiceImpl implements DummyService {
private static final Logger LOGGER = LoggerFactory.getLogger(DummyServiceImpl.class);
#Autowired
private DummyDao dummyDao;
public void saveDummydata() {
List<UserEntity> users = new ArrayList<UserEntity>();
for (int i = 1; i <= 10; i++) {
UserEntity user = new UserEntity();
user.setAge(i);
user.setUsername("username_" + i);
users.add(user);
}
this.dummyDao.update(users);
}
/* (non-Javadoc)
* #see com.bbb.core.objectify.test.services.DummyService#mutateDataWithException(java.lang.String, java.lang.Long)
*/
#Override
#ObjectifyTransactional
public void mutateDataWithException(String usernameToMutate, Long userIdToDelete) throws Exception {
//update one
LOGGER.info("Attempting to update UserEntity with username={}", "username_1");
List<UserEntity> mutatedUsersList = new ArrayList<UserEntity>();
List<UserEntity> users = dummyDao.findByUsername(usernameToMutate);
for (UserEntity userEntity : users) {
userEntity.setUsername(userEntity.getUsername() + "_updated");
mutatedUsersList.add(userEntity);
}
dummyDao.update(mutatedUsersList);
//delete another
UserEntity user = dummyDao.findByUserId(userIdToDelete).get(0);
LOGGER.info("Attempting to delete UserEntity with userId={}", user.getUserId());
dummyDao.delete(user.getUserId());
throw new RuntimeException("Dummy Exception");
}
/* (non-Javadoc)
* #see com.bbb.core.objectify.test.services.DummyService#findAllUsers()
*/
#Override
public List<UserEntity> findAllUsers() {
return dummyDao.loadAll();
}
Aspect which wraps the method annoted with ObjectifyTransactional as a transact work.
#Aspect
#Component
public class ObjectifyTransactionAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectifyTransactionAspect.class);
#Around(value = "execution(* *(..)) && #annotation(objectifyTransactional)")
public Object objectifyTransactAdvise(final ProceedingJoinPoint pjp, ObjectifyTransactional objectifyTransactional) throws Throwable {
try {
Object result = null;
Work<Object> work = new Work<Object>() {
#Override
public Object run() {
try {
return pjp.proceed();
} catch (Throwable throwable) {
throw new ObjectifyTransactionExceptionWrapper(throwable);
}
}
};
switch (objectifyTransactional.propagation()) {
case REQUIRES_NEW:
int limitTries = objectifyTransactional.limitTries();
if(limitTries <= 0) {
Exception illegalStateException = new IllegalStateException("limitTries must be more than 0.");
throw new ObjectifyTransactionExceptionWrapper(illegalStateException);
} else {
if(limitTries == Integer.MAX_VALUE) {
result = ObjectifyService.ofy().transactNew(work);
} else {
result = ObjectifyService.ofy().transactNew(limitTries, work);
}
}
break;
case NOT_SUPPORTED :
case NEVER :
case MANDATORY :
result = ObjectifyService.ofy().execute(objectifyTransactional.propagation(), work);
break;
case REQUIRED :
case SUPPORTS :
ObjectifyService.ofy().transact(work);
break;
default:
break;
}
return result;
} catch (ObjectifyTransactionExceptionWrapper e) {
String packageName = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
LOGGER.error("An exception occured while executing [{}.{}] in a transactional context."
, packageName, methodName, e);
throw e.getCause();
} catch (Throwable ex) {
String packageName = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
String fullyQualifiedmethodName = packageName + "." + methodName;
throw new RuntimeException("Unexpected exception while executing ["
+ fullyQualifiedmethodName + "] in a transactional context.", ex);
}
}
}
Now the problem code part that i see is as follows in com.googlecode.objectify.impl.TransactorNo:
#Override
public <R> R transact(ObjectifyImpl<O> parent, Work<R> work) {
return this.transactNew(parent, Integer.MAX_VALUE, work);
}
#Override
public <R> R transactNew(ObjectifyImpl<O> parent, int limitTries, Work<R> work) {
Preconditions.checkArgument(limitTries >= 1);
while (true) {
try {
return transactOnce(parent, work);
} catch (ConcurrentModificationException ex) {
if (--limitTries > 0) {
if (log.isLoggable(Level.WARNING))
log.warning("Optimistic concurrency failure for " + work + " (retrying): " + ex);
if (log.isLoggable(Level.FINEST))
log.log(Level.FINEST, "Details of optimistic concurrency failure", ex);
} else {
throw ex;
}
}
}
}
private <R> R transactOnce(ObjectifyImpl<O> parent, Work<R> work) {
ObjectifyImpl<O> txnOfy = startTransaction(parent);
ObjectifyService.push(txnOfy);
boolean committedSuccessfully = false;
try {
R result = work.run();
txnOfy.flush();
txnOfy.getTransaction().commit();
committedSuccessfully = true;
return result;
}
finally
{
if (txnOfy.getTransaction().isActive()) {
try {
txnOfy.getTransaction().rollback();
} catch (RuntimeException ex) {
log.log(Level.SEVERE, "Rollback failed, suppressing error", ex);
}
}
ObjectifyService.pop();
if (committedSuccessfully) {
txnOfy.getTransaction().runCommitListeners();
}
}
}
transactOnce is by code / design always using a single transaction to do things. It will either commit or rollback the transaction. there is no provision to chain transactions like a normal enterprise app would want.... service -> calls multiple dao methods in a single transaction and commits or rollbacks depending on how things look.
keeping this in mind, i removed all annotations and transact method calls in my dao methods so that they don't start an explicit transaction and the aspect in service wraps the service method in transact and ultimately in transactOnce...so basically the service method is running in a transaction and no new transaction is getting fired again. This is a very basic scenario, in actual production apps services can call other service methods and they might have the annotation on them and we could still end up in a chained transaction..but anyway...that is a different problem to solve....
I know NoSQLs dont support write consistency at table or inter table levels so am I asking too much from google cloud datastore?

Resources