MyBatis: how to map nested objecs - mybatis

I want to avoid xml files in my project and use only annotations. What I'm not understand is how to map nested object with MyBatis 3.5.
I have a POJO like this
public class Father {
private String name;
private int age;
private Son son;
}
public class Son {
private String name;
private int age;
}
How can I map name and age properties without xml files?
With #Results and #Result I can map father propery but I cannot use nested annotations.

I found the solution: MyBatis can access nested object in #Result annotation using the dot:
#Select([...])
#Results(value = {
#Result(property = "name", column = "name_db_colum"),
#Result(property = "age", column = "age_db_colum"),
#Result(property = "son.name", column = "son_name_db_colum"),
#Result(property = "son.age", column = "son_age_db_colum"),
})

Related

Sorting on embedded entity name in hibernate search

I have the following Entity definitions.
public class Order {
#Id
#DocumentId
private Long id;
#Field
#IndexedEmbedded(includePaths = {"name"})
#OneToOne
#JoinColumn(name = "ACCOUNT_ID")
private Account account;
// the rest are omitted for brevity purpose
}
public class Account {
#Id
#DocumentId
private Long id;
#SortableField(forField = "name_Sort")
#Field(name = "name_Sort", store = Store.YES, normalizer= #Normalizer(definition = SearchConstants.LOWER_CASE_NORMALIZER))
#Column(name = "NAME")
private String name;
}
If I search on Order and want to have the search results sorted by account name, is there good way of doing so possibly using the embedded indexed annotation? I know we can do it by adding an extra string field in Order that is called accountName, then just add sorting annotation on top of that. Is it possible to achieve this without specifying the sorting annotation in Order but just use the sorting annotation that is already defined in Account?
Change this:
#IndexedEmbedded(includePaths = {"name"})
To this:
#IndexedEmbedded(includePaths = {"name", "name_Sort"})
Then you can use the field account.name_Sort for sorts on orders:
QueryBuilder builder = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity( Order.class ).get();
Query luceneQuery = /* ... */;
FullTextQuery query = s.createFullTextQuery( luceneQuery, Order.class );
query.setSort(
builder.sort().byField("account.name_Sort").createSort()
);
List results = query.list();

How to query jsonb column using spring data specification?

I have below table temp_tbl (postgres):
ID(int) NAME(TEXT) LINKS(jsonb)
-------- ---------- -------------------
1 Name1 ["1","23","3", "32]
2 Name2 ["11","3","31", "2]
3 Name3 ["21","13","3", "12]
Now my native query to get rows which has 'LINKS' with value as "3" is:
select * from temp_tbl where links #> '["3"]'
returns rows 2 & 3.
I want implement this query using org.springframework.data.jpa.domain.Specification
I have implemented something like below where the jsonb column is not-an-array, but has key-value json using jsonb_extract_path_text. But the above column stores only values in an array.
My entity class.
#Entity
#Table(name = "temp_tbl")
public class TempTbl {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "LINKS", columnDefinition = "jsonb null")
#Convert(converter = JsonbConverter.class)
private List<String> linkIds;
}
I need help in translating above query into specification using criteriabuilder.
One way to check if a jsonb array contains a String using Jpa specifications is to use the functions jsonb_contains and jsonb_build_array, the latter of which is used to change the String into a jsonb array for use in jsonb_contains.
public static Specification<TempTbl> linkInLinks(String linkId) {
return (root, query, builder) -> {
Expression toJsonbArray = builder.function("jsonb_build_array", String.class, builder.literal(linkId));
return builder.equal(builder.function("jsonb_contains", String.class, root.get("linkIds"), toJsonbArray), true);
};
}

Check searchable field of an entity based on indexing

I have an entity defined as following.
public class Deal {
#Id
#DocumentId
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Field
#Column(name = "NAME")
private String name;
#Field
#Column(name = "ADVERTISER_NAME")
private String advertiserName;
#Field
#Column(name = "BRAND_NAME")
private String brandName;
//other fields and getters/setters omitted for brevity
}
If I want to search on all searchable fields, I can do something like the following, which demonstrates the usage of onField, onFields and andFields.
Query luceneQuery1 = mythQB
.simpleQueryString()
.onFields("name", "history", "description")
.matching("teststring")
.createQuery();
Query luceneQuery2 = mythQB
.simpleQueryString()
.onField("name")
.boostedTo(5f)
.andFields("advertiserName", "brandName")
.boostedTo(2f)
.withAndAsDefaultOperator()
.matching("teststring")
.createQuery();
If I add Index.NO to #Field(making an entity field not searchable),for example, change the annotation of brandName to #Field(index = Index.NO), now I only have two searchable fields: name and advertiserName, if we don't consider id. In this case, the example query above will throw runtime exception because it tries to search on brandName that is not searchable.
I've tried something like the following to dynamically get the full list of searchable fields based on whether a field has annotation or not. But this won't work if the index is Index.NO.
My question is that is there a way to dynamically get the full list of searchable fields based on the actual index value?
protected String[] getSearchableFields() {
List<String> fields = Lists.newArrayList();
Class<?> c = clazz;
while (c != null) {
for (Field field : c.getDeclaredFields()) {
if (field.isAnnotationPresent(org.hibernate.search.annotations.Field.class)
|| field.isAnnotationPresent(org.hibernate.search.annotations.Fields.class)) {
if (field.getType().isAssignableFrom(String.class)) {
fields.add(field.getName());
}
}
}
c = c.getSuperclass();
}
return fields.toArray(new String[fields.size()]);
}
There is a metadata API in Hibernate Search.
FullTextSession ftSession = ...;
IndexedTypeDescriptor indexedType = ftSession.getSessionFactory().getIndexedTypeDescriptor(clazz);
for (PropertyDescriptor property : indexedType.getIndexedProperties()) {
for (FieldDescriptor field : property.getIndexedFields()) {
if (field.getIndex() == Index.YES) {
// do something
}
}
}
See this section of the reference documentation for more information.
There are a few limitations, like not being able to "see" the extra fields contributed by custom bridges. But it works well apart from that.

get #ElementCollection entries with Querydsl

I have an #Entity class which holds an #ElementCollection:
#Entity
public class Skill extends JpaEntity {
#ElementCollection(targetClass = SkillName.class)
#CollectionTable(joinColumns = #JoinColumn(name = "SKILL_ID"))
private Set<SkillName> names = new HashSet<>();
...
Those elements are defined in a nested #Embeddable class without ID:
#Embeddable
#Immutable
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "NAME"))
public static class SkillName extends ValueObject {
private boolean selectable;
#Column(unique = true, nullable = false)
#Size(max = 64)
#NotEmpty
private String name;
...
I try to get some specific elements of that element-collection via Querydsl:
QSkill skill = QSkill.skill;
QSkill_SkillName skillName = QSkill_SkillName.skillName;
List<SkillName> foundSkillNames = from(skill)
.innerJoin(skill.names, skillName).where(...)
.list(skillName);
This gives me a MySQLSyntaxErrorException: Unknown column 'names1_.id' in 'field list' since the resulting query looks like:
select names1_.id as col_0_0_ from Skill skill0_ inner join Skill_names names1_ on ...
which is obviously wrong since SkillName has no id
If I replace .list(skillName) with .list(skillName.name) everything works fine, but I get a list of Strings instead of a list of SkillNames.
So the question is:
What can I do to get a list of #Embeddables of an #ElementCollection via Querydsl?
since you are looking for Embeddable objects inside an entity, you might navigate from the entity to the requested Embeddable (in your case "SkillName") - therefor your query should be changed to list(skill) - the entity:
List<Skill> list =
from(skill).innerJoin(skill.names, skillName).
where(skillName.name.like(str)).
list(skill);
for (Skill skill : list) {
// do something with
Set<SkillNames> skillNames = skill.getNames();
}
HTH
You cannot project Embeddable instances directly, but alternatively you can use
Projections.bean(SkillName.class, ...) to populate them or
Projections.tuple(...) to get the skillName properties as a Tuple instance

Using COUNT in JPQL Query

I have the following JPQL query:
List<DestinationInfo> destinations = em.createQuery("SELECT NEW com.realdolmen.patuva.dto.DestinationInfo(d.name, d.continent, MIN(t.departureDate), MIN(t.pricePerDay), COUNT(t.id))" +
" FROM Destination d, Trip t" +
" WHERE d.continent = :continent " +
" GROUP BY d.name, d.continent").setParameter("continent", searchedContinent).getResultList();
If I run this I get the error:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [com.realdolmen.patuva.dto.DestinationsList]
If I leave out the COUNT(t.id) and remove that parameter from my DestinationInfo constructor it works fine. Why can't I map the COUNT(t.id) to my DestinationInfo DTO.
This is my DestinationInfo class:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private Integer totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, Integer totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}
Apparently COUNT(t.id) returns a number of type long. Changing the DestinationInfo class to the following makes it work:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private long totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, long totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}