Using custom BeanUtils.copyProperties method causes Hibernate to delete Collection - spring

I subclassed org.apache.commons.beanutils.BeanUtilsBean so that it ignores NULL properties:
public class NullAwareBeanUtilsBean extends BeanUtilsBean {
Logger log = Logger.getLogger(this.getClass());
#Override
public void copyProperty(Object dest, String name, Object value) throws IllegalAccessException, InvocationTargetException {
if (value == null) {
log.debug("skipping property " + name);
return;
}
super.copyProperty(dest, name, value);
}
}
My user class has a Collection of Stats:
public class User implements Serializable{
private Integer id;
private String username;
private Set<Stat> stats = new HashSet<Stat>();
}
If I do this it works fine:
public void updateUser(User user) {
User dbUser = userDao.read(user.getId());
dbUser.setUsername(user.getUsername());
log.debug("about to save" + dbUser);
userDao.update(dbUser);
}
But if I use copyProperties() it thinks that the Set of Stats is empty and tries to delete it:
public void updateUser(User user) {
User dbUser = userDao.read(user.getId());
//the problem here is that hibernate does not like the copyProperties method...
try {
NullAwareBeanUtilsBean nullAwareBeanUtilsBean = new NullAwareBeanUtilsBean();
nullAwareBeanUtilsBean.copyProperties(dbUser, user);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//dbUser = (User) userDao.getCurrentSession().merge(dbUser);
//log.debug("stats size=" + dbUser.getStats().size());
log.debug("about to save" + dbUser);
userDao.update(dbUser); }
I have tried using Hibernate.initialize(), as well as referring to the Set before and after using BeanUtils to init the Set (which it does if called before), but it doesn't matter because it is empty after (even if I re-init)... I also tried merging it into the Session but that didn't work either. Any thoughts? I'm thinking it might have to do with Hibernate creating a proxy object and BeanUtils somehow messing that up.

I think that the problem can be that "user" has an empty set (not null object), so copyProperties is copying the empty Set of "user" into the existing Set with values of "dbUser" (so, when you save the dbUser, you are cleaning the Set). If you want to prevent also the copy of and empty set, you can change your method to:
public void copyProperty(Object dest, String name, Object value) throws IllegalAccessException, InvocationTargetException {
if (value == null) {
log.debug("skipping property " + name);
return;
}
if ((value instanceof Set) && ((Set) value).isEmpty()) {
log.debug("skipping empty Set" + name);
return;
}
super.copyProperty(dest, name, value);
}

Related

JSF Validation using DAO doesn't work

I'm trying to validate the information, user is giving at the registration. One of the fields contains the mailadress, which should be validated by looking in the database to confirm, it doesn't exist yet.
Problem is, that if i type an existing mailadress, it will give back a NonUniqueResultException, but also does store the new user with the duplicate mailadress in the database. Don't understand this, beacuse in the JSF-lifecycle after validation fails, it shouldn't go on to the invoke application phase, right?
Here's my code:
mail field in register formular
<b:inputText id="mail" required="true"
requiredMessage="Bitte geben Sie Ihre E-Mail-Adresse an!"
label="E-Mail" placeholder="name#example.com" value="#{registrierenManagedBean.nutzer.mail}">
<f:validator validatorId="mailValidatorRegistrieren"/>
<b:messages for="mail"/>
</b:inputText>
my custom validator
#FacesValidator("mailValidatorRegistrieren")
public class MailValidatorRegistrieren implements Validator {
#EJB
private DAO dao;
private String mail;
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
#Override
public void validate(FacesContext facesContext, UIComponent uiComponent, Object o) throws ValidatorException {
mail = (String)o;
boolean matchesPattern = EMAIL_PATTERN.matcher(mail).find();
if(!matchesPattern)
{
throw new ValidatorException((new FacesMessage("Invalid mail")));
}
if(mail.isEmpty()) {
return;
} else if(validateNutzer(mail)){
throw new ValidatorException(new FacesMessage("mail alredy used"));
} else{
return;
}
}
private boolean validateNutzer(String mail) {
try {
Nutzer n = dao.findNutzerByMail(mail);
return n.getMail().equals(mail);
} catch (NullPointerException e) {
return false;
}
}
}
and the "findNutzerByMail"-method from my DAO
public Nutzer findNutzerByMail(String mail) {
try {
return em.createNamedQuery("findNutzerByMail", Nutzer.class)
.setParameter("mail", mail)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}

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?

What is happening internally for auto increment field in data base side and application side?

I have a bean for spring application
#Entity
#Table(name="tbl_apply_leave")
public class ApplyLeaveModel{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int dbid;
private String employee_name;
public int getDbid() {
return dbid;
}
public void setDbid(int dbid) {
this.dbid = dbid;
}
public String getEmployee_name() {
return employee_name;
}
public void setEmployee_name(String employee_name) {
this.employee_name = employee_name;
}
}
then In controller before save this object value of dbid is null.
but after save the object using hibernate dbid contains value.
I don't know what's happening.
controller code is:
System.out.println(applyLeaveModel.getDbid()); // null
leave_dao.saveApplyLeaveModel(applyLeaveModel);
System.out.println(applyLeaveModel.getDbid()); // 5
public void saveApplyLeaveModel(ApplyLeaveModel applyLeaveModel) {
Session session = null;
Transaction trans_obj = null;
try {
session = sessionFactory.openSession();
if (session.isOpen() && session != null) {
trans_obj = session.beginTransaction();
session.persist(applyLeaveModel);
}
} catch (Exception e) {
System.out.println("save ApplyLeaveModel session " + e);
} finally {
trans_obj.commit();
session.close();
}
}
Hibernate executes PreparedStatement.getGeneratedKeys() to obtain the generated id after insert.
Also related: hibernate.jdbc.use_get_generated_keys.
Sidenote: Primitive types in Java cannot have null value. Probably you mean 0, or type of the id is Integer.

Choose Class in Birt is empty eventhough I have added jar in Datasource

Even though while creating dataset choose class window is empty. I am using Luna Service Release 2 (4.4.2).
From: http://yaragalla.blogspot.com/2013/10/using-pojo-datasource-in-birt-43.html
In the dataset class the three methods, “public void open(Object obj, Map map)”, “public Object next()” and “public void close()” must be implemented.
Make sure you have implemented these.
Here is a sample that I tested with:
public class UserDataSet {
public Iterator<User> itr;
public List<User> getUsers() throws ParseException {
List<User> users = new ArrayList<>();
// Add to Users
....
return users;
}
public void open(Object obj, Map<String, Object> map) {
try {
itr = getUsers().iterator();
} catch (ParseException e) {
e.printStackTrace();
}
}
public Object next() {
if (itr.hasNext())
return itr.next();
return null;
}
public void close() {
}
}

Spring BigDecimal input

I hava a POJO object with one BigDecimal field sum.
In controller I add this POJO object as form like this:
MyForm form = new MyForm();
model.addAttribute("command", form);
My jsp:
<form:input path="sum" size="27"/>
In controller i add initbinder:
binder.registerCustomEditor(BigDecimal.class, new SumEditor());
Part of my SumEditor class:
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(parseMoney(text));
}
private BigDecimal parseMoney(String str) {
try {
return new BigDecimal(str);
} catch (Exception e) {
logger.error("error", e);
}
return null;
}
But in JSP view I see (in input field): |null________|
How fix this? I need: |___________|
You should simply override getText method of SumEditor to have it return an empty string ("") for a null value :
#Override
public String getAsText() {
if (getValue == null) {
return "";
}
BigDecimal val = (BigDecimal) getValue();
return val.toStr(); // or whatever conversion you need
}

Resources