How to fill outer and inner classes - spring

import lombok.Data;
#Data
public class P {
private String name;
private String operationId;
#Data
public static class Operation {
private Timestamp ts;
private String id;
}
}
I want to fill in the fields of both classes with one query using join
<select id="getAllP" resultType="com.mappers.P">
SELECT t."name" as name, t."operationId" as operationId, o."ts" as ts, o."id" as id
FROM FROM "P" t JOIN "Operation" o ON t."operationId" = o."id"
</select>
If leave resultType = "com.mappers.P" in this form, then only the field of the outer class is filled, if resultType = "com.mappers.P & Operation" then only the internal one, how to ensure that the fields of both classes are filled? thanks in advance

Something like this, although you can go deeper by splitting them into two result maps and building them that way. Use the documentation above, and you can get super creative.
<resultMap id="pMap">
<result property="name" column="name"/>
<association type="Operation" columnPrefix="o_">
<result property="name" column="name"/>
<result property="ts" column="name"/>
</association>
</resultMap>
<select id="getAllP" resultMap="pMap">
...
</select>

Related

How to properly map MyBatis parent relation?

I'm currently struggling on trying to map my class using MyBatis and Oracle database.
I have a table with columns ID, CODE, DESCRIPTION, PARENT, VALUE, UNITS
I have a model:
public class Parameter {
private String code;
private String description;
private String value;
private Parameter parent;
private String units;
<...>
}
and a xml mapper parameterMapper.xml:
<resultMap id="ParameterResultMap" type="Parameter">
<id property="id" column="id" />
<result property="code" column="code" />
<result property="description" column="description" />
<result property="value" column="value" />
<result property="units" column="units" />
<association property="parent"
resultMap="persistence.ParameterMapper.ParameterResultMap"
columnPrefix="parent_" />
</resultMap>
<select id="readParameter" parameterType="Parameter" resultMap="ParameterResultMap">
SELECT
p.id,
p.code,
p.description,
p.value,
p.units,
par.id AS parent_id,
par.code AS parent_code,
par.description AS parent_description,
par.value AS parent_value,
par.units AS parent_units
FROM parameter p
LEFT JOIN parameter par ON par.code = p.parent
</select>
And I get java.lang.IllegalArgumentException: Result Maps collection does not contain value for persistence.ParameterMapper.ParameterResultMap] exception on my console.
What I've done wrong and where could be the problem?
Thanks in advance!

Spring MVC complex object data binding

I am still struggling with Spring MVC with what should be a fairly straightforward problem but what seems to be sparsly documented in Spring MVC documentation.
My project uses Spring MVC and Thymeleaf for the views, but the view rendering engine is not really relevant to the problem.
My application is centered around an Activity class which models an (indoor or outdoor) activity which is organized by a member and where fellow members can subscribe to. An Activity has, among others, a Category field and a Region field, which are dropdown fields which are modeled by Hibernate as many-to-one entities to DB lookup tables which contain an id and description field.
The code for the Activity entity class is as follows, the non relevant fields are omitted to shorten the code:
package nl.drsklaus.activiteitensite.model;
//imports
#Entity
#Table(name="activity")
public class Activity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="organizer_id")
private Member organizer;
#Size(min=5, max=50)
#Column(name = "title", nullable = false)
private String title;
#Size(min=5, max=500)
#Column(name = "description", nullable = false)
private String description;
#ManyToOne
#JoinColumn(name="category_id")
private ActivityCategory category;
#ManyToOne
#JoinColumn(name="region_id")
private ActivityRegion region;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name="member_activity_subscription",
joinColumns = {#JoinColumn(name="activity_id")},
inverseJoinColumns={#JoinColumn(name="member_id")})
private List<Member> participants = new ArrayList<Member>();
//getters and setters
#Override
public int hashCode() {
...
}
#Override
public boolean equals(Object obj) {
...
}
}
In the view, the user should be able to select a Region and Category from a select box. THe options are put in the Model using a #ModelAttribute annotated method on the class level.
THe problem is with the binding of the box to the lookup property fields.
For example the Category field is of the ActivityCategory type, which is an entity class containing an id and a description property.
In the view, the select box is filled with the list of possible options (allCategories which contains ActivityCategory instances), Thymeleaf takes care of selecting the current value by matching the "value" attribute value with the list:
<label>Categorie</label>
<select th:field="*{category}">
<option th:each="cat : ${allCategories}"
th:value="${cat}"
th:text="${cat.description}">
</option>
</select>
The generated HTML looks like:
<select id="category" name="category">
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory#20">Actief en sportief</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory#21">Uitgaan en nachtleven</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory#22" selected="selected">Kunst en cultuur</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory#23">Eten en drinken</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory#24" selected="selected">Ontspanning en gezelligheid</option>
</select>
As we see, the value attributes contain a string representation of the object itself which is clearly not desired, to show the id values we could use ${cat.id} instead of ${cat} but then the selection of the current value (setting the 'selected="selected"' attribute) does not work anymore. THerefore I implemented a Converter which converts an ActivityCategory object to an int (the id value). In Thymeleaf, the converter is called by using the double accolades {{}}:
th:value="${{cat}}"
THe converter is created and added to Spring:
public class LookupConverter implements Converter<LookupEntity, String> {
public String convert(LookupEntity source) {
return String.valueOf(source.getId());
}
}
//In MvcConfig class
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LookupConverter());
}
Now the HTML shows the id values for the options, which is much more logical:
<select id="category" name="category">
<option value="1">Actief en sportief</option>
<option value="2">Uitgaan en nachtleven</option>
<option value="3" selected="selected">Kunst en cultuur</option>
<option value="4">Eten en drinken</option>
<option value="5">Ontspanning en gezelligheid</option>
</select>
But it still wrong after submitting, the id value cannot be bound to the Activity object which expects a ActivityCategory instead if an integer value, so a typeMismatch validation error is generated.
My handler method looks like:
#RequestMapping(value = "/{id}/submit", method = RequestMethod.POST)
public String submitForm(#ModelAttribute("activity") Activity activity, BindingResult result, ModelMap model) {
if (result.hasErrors()) {
return "activityform";
} else {
if (activity.getId() == null) {
this.service.saveActivity(activity);
} else {
this.service.mergeWithExistingAndUpdate(activity);
}
return "redirect:/activity/" + activity.getId() + "/detail";
}
}
I have looked at many posts but still found have no solution for this IMHO pretty trivial issue. How can the String value containing the id be accepted by the handler method and properly converted? Or can we not use the id value for this purpose?
Looking for some hints...
I think you can't use your entity model to submit data from your form to the MVC controller. Try to create a separate form object that matches the form data and write a service method to translate it to entities that can be persisted in the database.
With help from another forum I have found the most elegant solution! Instead of a Converter, we use a Formatter which can convert from specfiec Object type to a String and vice versa. The formatter is registered to Spring and automatically called from Thymeleaf and converts the id field to an ActivityCategory instance with only the id value set. So we do not lookup the actual instance from the database because we do not need the description here, for Hober ate the id is enough to create the query.
My formatter looks like:
public class ActivityCategoryFormatter implements Formatter<ActivityCategory> {
#Override
public String print(ActivityCategory ac, Locale locale) {
// TODO Auto-generated method stub
return Integer.toString(ac.getId());
}
#Override
public ActivityCategory parse(final String text, Locale locale) throws ParseException {
// TODO Auto-generated method stub
int id = Integer.parseInt(text);
ActivityCategory ac = new ActivityCategory(id);
return ac;
}
}
and is registered to Spring (together with the ActivityRegionFormatter for the other lookup field) by:
#Override
public void addFormatters(FormatterRegistry registry) {
//registry.addConverter(new LookupConverter());
registry.addFormatter(new ActivityCategoryFormatter());
registry.addFormatter(new ActivityRegionFormatter());
}
And now it works as expected!
The only remaining issue is that we have some code duplication because the two Formatter classes are almost the same, they only differ in the generic class that is passed in.
I tried to solve this by using a common interface LookupEntity which is implemented by the two lookup entity classes (ActivityCategory and RegionCategory) and use this common interface to define the formatter but unfortunately that did not work...

how to map front-end field names to db column names for sorting

I am working on a web app using Spring MVC 3, Spring Data Commons 1.4.1, and MyBatis 3. To support sorting, I find myself needing to map input field names used on the client side to names of corresponding columns in the database. For example, on the client side, an input field is called shortName while the name of the corresponding column in the database is SHORT_NAME. What is the best way to do it? Does Spring provide any support for this? Thanks.
Here's how I annotate field names in the domain class so that I can look them up later in the controller method below.
#Entity
public class Activity extends BaseDomainObject {
#Column(name="ID")
private Long id;
private String name;
#Column(name="SHORT_NAME")
private String shortName;
#Column(name="START_TIME")
private Date startTime;
#Column(name="END_TIME")
private Date endTime;
#Column(name="LOCATION")
private String location;
public Activity()
{
}
// getters and setters go here
}
Here's the method in the controller
public #ResponseBody Page<Activity> query(ActivityCriteria ac, Pageable p) {
// translate the fiend name used in the front-end into the one used in the back-end
List<Order> orders = new ArrayList<Order>();
java.util.Iterator<Order> iterator = p.getSort().iterator();
while (iterator.hasNext()) {
Order order = iterator.next();
if (!StringUtils.isBlank(order.getProperty()) && order.getDirection() != null) {
String columnName = mapFieldNameToColumnName(Activity.class, order.getProperty());
Order newOrder = new Order(order.getDirection(), columnName);
orders.add(newOrder);
}
}
Pageable copy = new PageRequest(p.getPageNumber(), p.getPageSize(), new Sort(orders));
return activityService.query(ac, copy);
}
Here's what I end up doing in the corresponding MyBatis mapper file.
<select id="getListOfActivitiesWithConditions" resultMap="activityMap" parameterType="map">
SELECT a.* FROM activity a
<include refid="search-conditions"/>
<if test="page.sort != null">
<foreach item="order" index="index" collection="page.sort" open="order by" separator="," close=" ">
<if test="order.property != null and order.property == 'shortName'">
SHORT_NAME ${order.direction}
</if>
<if test="order.property != null and order.property == 'startTime'">
START_TIME ${order.direction}
</if>
</foreach>
</if>
limit #{page.offset}, #{page.size}
</select>

JPA | One to one mapping with eager fetch

I am working with a JPA sample application with hibernate as JPA provider.
In which I am having a one to one mapping with fetch type EAGER.
Here below is my code block:
Person.java
public class Person extends Persistent implements Serializable {
private static final long serialVersionUID = 1L;
String name;
// address as composition
Address address;
String addressId;
Address.java
public class Address extends Persistent implements Serializable {
private static final long serialVersionUID = 1L;
String address;
mapping.xml
<entity name="Person" class="com.csam.mtp.wallet.wallet.Person"
access="FIELD">
<table name="person" />
<attributes>
<basic name="name">
<column name="name" length="36" nullable="false" />
</basic>
<basic name="addressid">
<column name="addressid" length="36"/>
</basic>
<one-to-one name="address" fetch="EAGER">
<join-column name="addressid"
referenced-column-name="id" insertable="false" updatable="false"
nullable="false" />
</one-to-one>
</attributes>
</entity>
<entity name="address" class="com.csam.mtp.wallet.wallet.Address"
access="FIELD">
<table name="address" />
<attributes>
<basic name="address">
<column name="address" length="36" />
</basic>
</attributes>
</entity>
DB Script
CREATE TABLE address (
id VARCHAR (50) not null,
address VARCHAR(50),
primary key(id)
);
CREATE TABLE person (
id VARCHAR (50) not null,
name VARCHAR(50),
addressid VARCHAR(50),
primary key(id),
foreign key (addressid) REFERENCES address(id),
);
Driver class
Address address = new Address();
address.setAddress("test");
address = addressRepository.save(address);
Person person = new Person("Mike");
//person.setAddressId(address.id());
try {
person = personRepository.save(person);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(personRepository.exists(person.getID()));
Person savedPerson = personRepository.findOne(person.getID());
// Asserts
assertNotNull(savedPerson);
assertNotNull(savedPerson.id());
While saving person If i don't set address id then it saves person successfully, exists returns true but find one api always returns null
while saving person if first I save address and set address id in person and save it then
It saves person successfully, exists return true and find one api return Person object with address object (as it is eager)
So here below is my query,
Is it in case of one to one mapping with eager fetch on child, child must be saved and set in parent before the saving of parent?
I tried to find out related arcticle but not able to get one, Please assist.
Thanks in advance
In your case the Address is the parent and the Person is the Child. That's because the Child points to the Parent through the addressId FK.
Because you don't have a bi-directional association, you shouldn't have insertable="false" updatable="false"
With these settings, Hibernate won't be able to include the addressId in the INSERT or the UPDATE query.
Remove insertable="false" updatable="false"
Remove the addressId column too, since you can reference it through address.id
Add cascade from Person to Address:
<one-to-one name="address" fetch="EAGER">
<join-column name="addressid"
referenced-column-name="id" nullable="false" />
<cascade>
<cascade-persist/>
<cascade-merge/>
</cascade>
</one-to-one>
Probably it would be better to have a bi-directional association so that an Address has also a reference to a Person and in that case you should probably have a one-to-many association, since an Address may have multiple Persons.
Finally got the solution with nullable=true, as below:
<one-to-one name="address" fetch="EAGER">
<join-column name="addressid"
referenced-column-name="id" insertable="false" updatable="false"
nullable="true" />
</one-to-one>

How to add Restrictions to Criteria API in Hibernate

I am newbie to Hibernate and Spring. My question is on “ how to add criteria…” in situation like, I have two beans:
1) Language:
public class Language {
private int languageId;
private String languageName;
private List<Topic> listOfAllTopics;
}
2) Topic:
public class Topic {
private int topicId;
private String topicName;
}
My database tables:
1) language
language_id int(PK)
language_name varchar(30)
2) topic
topic_id int(PK)
topic_name varchar(30)
language_id int(FK)
Hibernate mapping xml files are:
1) Language.hbm.xml
<hibernate-mapping package="com.mw.javamentordb.dto">
<class name="Language" table="language" lazy="true">
<id name="languageId" column="language_id" type="int">
<generator class="increment" />
</id>
<property name="languageName" column="language_name"type="string"/>
<bag name="listOfAllTopics" cascade="all">
<key column="language_id" not-null="true" />
<one-to-many class="Topic"/>
</bag>
</class>
</hibernate-mapping>
2) Topic.hbm.xml
<hibernate-mapping package="com.mw.javamentordb.dto">
<class name="Topic" table="topics" lazy="true">
<id name="topicId" column="topics_id" type="int">
<generator class="increment" />
</id>
<property name="topicName" column="topics_name" type="string" />
</class>
</hibernate-mapping>
And I get all lang in my database using this method and it works properly.
public List<Language> getAllLanguages() {
Criteria criteria = getSession().createCriteria(Language.class);
return criteria.list();
}
But when I try to get all topics of particular language(by langId) using criteria, it not works.
Actually I don’t know how to use criteria in such kind of situation..
public List<Topic> getAllTopicOfLanguage(Language language) {
Criteria criteria = getSession().createCriteria(Topic.class);
criteria.add(Restrictions.eq("?");
return criteria.list();
}
Your table cheme for Topic has a foreign key constraint language_id but your class and hbm.xml mapping does not.
So your desired query is not possible.
Change it to:
public class Topic
{
private int topicId;
private String topicName;
private Language language;
}
And at the property to hbm.xml:
<many-to-one name="language" class="package.Language" fetch="select">
<column name="language_id">
</many-to-one>
Then you can query it using criteria like following:
criteria.add(Expression.eq("language.language_id", language.getLanguageId()));
Alternatively you could use the equality on the object itself instead their id's or use Expression.eqId(Object Object)
Advice:
Use a abstract superclass with the identifier field to make things more generic. Naming the identifier once topicId on class and table Topic and languageId on class and table Language just is overhead. Just use id on property, class and table to make things easier.
In larger applications this will become more obvious.
you are searching your result through wrong way, as per your design, you will get the result through,
public Language getAllTopicOfLanguage(Language language) {
Criteria criteria = getSession().createCriteria(Language.class);
criteria.add(Expression.eq("languageId",language.getLanguageId()));
return (Language)(criteria.list().get(0));
}
this will return you Language DTO object, with list of topics matching the language id filled in Language DTO only.
as you are newbie, just want to tell you that when you are using hibernate, your DTO and table design should be very precise.

Resources