0

I am building a MVC application using thymeleaf and Spring and Hibernate. My question here is more about hibernate than spring.

This is what i have so far.

A UI

<form role="form" th:action="@{/user/{userId}/official(userId=${userId})}" th:object="${user}" method="post">

            <!--  first form group  -->
            <div class="form-group">

                <label class="control-label col-xs-2">First Name</label>
                <div class="col-xs-2">
                    <input type="text" class="form-control" th:field="*{firstName}" placeholder="First Name" />
                </div>

                <label class="control-label col-xs-2">Last Name</label>
                <div class="col-xs-3">
                    <input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name" />


            <!--  first form group end  -->
            </div>
            <br/><br/>

            <!--  third form group  -->
                <div class="form-group">
                    <label class="control-label col-xs-2">Email Address</label>
                    <div class="col-xs-2">
                        <input type="text" class="form-control" th:field="*{emailAddress}" placeholder="Email Address" />
                    </div>

            </div>

            <div class="form-group">
                                    <div class="col-xs-2">
                                        <input type="submit" value="Update" class="btn btn-primary" />
    </form>

Controller :

@Controller
public class UserController {

    @Autowired
    private IUserService userServiceImpl;

    @RequestMapping(value = "/user/{userId}/official", method = RequestMethod.GET)
    public String getUserOfficialInfo(@PathVariable("userId") Integer userId, Model model) throws ServiceBusinessException {

                    UserBO userBO = userServiceImpl.findUserByUserId(userId);
                    model.addAttribute("user", userBO);
                    model.addAttribute("userId", userId);
                    model.addAttribute("genders", EnumSet.allOf(Gender.class));
                    return "official";
    }


    @RequestMapping(value = "/user/{userId}/official", method = RequestMethod.POST)
    public String updateUserOfficialInfo(@PathVariable("userId") String userId, @ModelAttribute("user") UserBO user,BindingResult result, Model model) throws ServiceBusinessException {

                    userServiceImpl.updateUser(user);
                    UserBO userBO = userServiceImpl.findUserByUserId(Integer.parseInt(userId));
                    model.addAttribute("user", userBO);
                    model.addAttribute("userId", userId);
                    model.addAttribute("genders", EnumSet.allOf(Gender.class));
                    return "official";
    }
}

DAO :

@Override
    public void updateUser(UserEntity user) throws DaoException {
        entityManager.merge(user);
    }

The GET method in the controller, gets the user object to the view. But on the VIew i am just displaying few of those attributes of a user object in the form.

On the form Submit, the POST method in the controller gets called, which calls the service layer and then the merge method in the DAO gets executed.

Now what I have observed is that this merge method on the entity manager is updating the attributes which are not there in the form to null.

I think this is the expected behaviour since the object is detached when its called from the POST method. So the right thing to do here is to first fetch the entity object from the database and then to that object set the fields which are changed in the form and then call the merge method.

Can some one let me know if the above what I said is correct ?

If yes, then my next question would be that isnt this quite tedious and kind of bit more effort. I mean there are going to be cases where in I would not want to display the entire object in the form. Also not in hidden fields. I am quite surprise that there is no way to handle this and I have to follow the approach I just described above each time.

Is there a better way to do this ? Wouldn't i just use JDBC template instead ? I know I would be writing boiler plate code there but I am kind of writing getters and setters here as well for each round trip to the UI.

user641887
  • 1,506
  • 3
  • 32
  • 50
  • Hibernate doesn't know anything about forms. Typical pattern I've seen is to load the entity from the db, map the fields from the form to the entity, and then persist the entity. I believe there's some spring plumbing somewhere to help with this, but I can't recall specifics google should find it. – Taylor Nov 16 '17 at 18:12

3 Answers3

1

Consider annotating your entity with org.hibernate.annotations.Entity.

@Entity
@Table(name = "user")
@org.hibernate.annotations.Entity(
        dynamicInsert = true, dynamicUpdate=true
)
public class User implements java.io.Serializable {
 // your properties
}

If you are using a 4.x version of Hibernate, you may want to use @DynamicUpdate instead since the usage of @org.hibernate.annotations.Entity has been deprecated recently.

References:

https://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/ https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/Entity.html https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/DynamicInsert.html https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/DynamicUpdate.html

Srikanth Anusuri
  • 1,234
  • 9
  • 16
0

you can put the following code in a util class and invoke it when you want to fill your object based on another reference object:

public static <T> void  fillNotNullFields(T reference, T source) {
        try {
            Arrays.asList(Introspector.getBeanInfo(reference.getClass(), Object.class)
                    .getPropertyDescriptors())
                    .stream()
                    .filter(pd -> Objects.nonNull(pd.getReadMethod()))
                    .forEach(pd -> {
                        try {
                            Object value = pd.getReadMethod().invoke(source);
                            if (value != null)
                                pd.getWriteMethod().invoke(reference, value);

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }
    }

So, you can do this on the service:

public UserBO updateUser(String userId, UserBO user ) {
                    UserBO reference = findOne(Integer.parseInt(userId));
                    fillNotNullFields(reference, user);
                    return updateUser(reference);
    }

I found the suport for this answer here. Hope it helps.

Lucas Gonzaga
  • 86
  • 1
  • 3
  • yes but that is not the point, the point is, if this is the expected behaviour of hibernate, i would like to know how other people are handling this ? if this is not the expected behavior what should happen in this case ? – user641887 Nov 16 '17 at 21:00
  • @user641887 In this case I actually don't know. I have use experience on hibernate only in restful web servides using the DTO design pattern, and this problem is not related to rest approach.Acctually in rest is not a good pratice to persist objects directly from your view layer. – Lucas Gonzaga Dec 01 '17 at 17:43
0

You will need to store the unnecessary bean properties in hidden fields so that they get remapped when the form is posted to the controller. For example, if you do not want to show the date of birth on the page (assuming that date of birth is already an attribute of the user), simply add the following tag inside the form.

<input type='hidden' th:field="*{dateOfBirth}" />
Srikanth Anusuri
  • 1,234
  • 9
  • 16
  • I dont want to do that precisely, for a user i have multiple attributes, much more than what I have in the form.. that would be completely redundant .. dont u think so ? – user641887 Nov 16 '17 at 22:49
  • Its a very commonly used strategy in most frameworks (such as Grails). If you really want to avoid putting multiple hidden fields you may want to look in to dynamic inserts. https://www.mkyong.com/hibernate/hibernate-dynamic-insert-attribute-example/ – Srikanth Anusuri Nov 16 '17 at 22:58
  • but mine is an update operation on an existing record .. not an insert – user641887 Nov 16 '17 at 23:01
  • The annotation supports both inserts and updates. I will add a new answer below based on this. Here is the related [Javadoc](https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/annotations/Entity.html) – Srikanth Anusuri Nov 16 '17 at 23:03