Hello I have following classes:
public class OrderTemplate {
<other fields>
#OneToMany(mappedBy = "orderTemplate", cascade = CascadeType.ALL)
#Column(name="order_template_input")
private Set<OrderTemplateInput> orderTemplateInputs;
}
Now i have Abstract Class orderTemplateInput like this:
public abstract class OrderTemplateInput {
<other fields>
#Column(name="text", nullable = false)
protected String text;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "order_template_id")
#JsonIgnore
protected OrderTemplate orderTemplate;
}
And two classes extends this one:
public class OrderTemplateTextInput extends OrderTemplateInput {
#JsonProperty(value="type")
public OrderTemplateInputType getType() {
return OrderTemplateInputType.TEXT;
}
}
And this one:
public class OrderTemplateSelectInput extends OrderTemplateInput {
#OneToMany(mappedBy = "orderTemplateSelectInput", cascade = CascadeType.ALL)
private Set<InputValue> inputValues;
#JsonProperty(value="type")
public OrderTemplateInputType getType() {
return OrderTemplateInputType.SELECT;
}
}
First of all i want to avoid fetchType.Eager. So i want to get all orderTemplate with associations collections. I have following hql query:
#Query("SELECT distinct o FROM OrderTemplate o JOIN FETCH o.orderTemplateInputs JOIN FETCH o.orderTemplateInputs oIn JOIN FETCH oIn.inputValues")
It's not working only in one case when the OrderTemplate has only collection of OrderTemplateTextInput. In the list of OrderTemplate there is only OrderTemplate with OrderTemplateSelectInput and with both select and text. How should look like the query to get all associations collections?
Related
I have the following problem: I have three connected classes. I have annotated them but I am getting wrong results (described below):
#Entityd
#Table(name = "ClassA")
public class ClassA{
#Id
#GeneratedValue
private Long id = 0L;
...
#OneToMany(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#Fetch(FetchMode.SELECT)
#Column(name = "ClassBList")
private List<ClassB> listB;
...
}
#Entity
#Table(name="ClassB")
public class ClassB {
#Id
#GeneratedValue
private Long id = 0L;
...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#Column(name = "ClassCList")
private List<ClassC> listC;
...
}
#Entity
#Table(name="ClassC")
public class ClassC {
#Id
#GeneratedValue()
private Long id = 0L;
...
#ElementCollection
private List<String> listD;
...
}
When I work with this structure for the first ClassA I create,save and load everything is ok. For a new instance of ClassA which I save to repo and load again, I suddenly have the strings of the first ClassA in listD.
The result I need is that every class is "independently" saved. So the collections of each class should hold unique (each one with its own id and sublists) objects.
What would be the best way (annotations) to model this classes in Java 8 with Spring Boot 2.2.0.M5 and javax.persistence-api 2.2 ?
EDIT:
I have now removed class B and rewrote classA to:
#Entity
#Table(name = "ClassA")
public class ClassA{
#Id
#GeneratedValue
private Long id = 0L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
#MapKey(name = "type")
private Map<String,Set<ClassC>> classCmap;
...
}
This is giving me an error like:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class
How can I model/solve/annotate this?
If you don't need to query data based on listD, I would suggest to keep the list as text in the database and use a converter:
#Converter
public class ListDConverter implements AttributeConverter<List<String>, String> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convertToDatabaseColumn(List<String> listD) {
try {
return objectMapper.writeValueAsString(listD);
} catch(IOException e){
return null;
}
}
#Override
public List<String> convertToEntityAttribute(String stringListD) {
if(stringListD == null){
return Collections.emptyList();
}
try {
return objectMapper.readValue(stringListD, new TypeReference<List<String>>() {});
}catch(IOException e){
return Collections.emptyList();
}
}
}
and in your ClassC entity class :
#Convert(converter = ListDConverter.class)
private List<String> listD;
Why do I like this approach :
No extra table and joins => better performance
Easier to read listD in the database
#ElementCollection describes a table. So your code is probably creating a "listD" table with one column of type string, with no primary key.
Also, do you really want to use the SELECT fetch mode? That's going to generate 1 + b + b*c queries when you could just implement your data as sets (since you have unique identifiers) and use JOIN, which would result in one and only one query.
See this site for an explanation on how to use #ElementCollection.
my superclass is:
#Entity
#Table(name = "TEST_VEHICLE")
#ChangesListener
#AttributeOverride(name = "id", column = #Column(name = "VEHICLE_ID"))
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "VEHICLE_TYPE_ID", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Vehicle extends ParentEntity {
#Column(name = "MAX_SPEED", nullable = false)
private Integer maxSpeed;
public Integer getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(Integer maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
and subclass is:
#Entity
#Table(name = "TEST_BUS")
#DiscriminatorValue("2")
public class Bus extends Vehicle {
#Column(name = "PASSENGER_NUMBER", nullable = false)
private Short passengerNumber;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FOO_OF_VEHICLE")
private Foo foo;
public Short getPassengerNumber() {
return passengerNumber;
}
public void setPassengerNumber(Short passengerNumber) {
this.passengerNumber = passengerNumber;
}
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
}
using fetch of foo on Root<Vehicle> in criteria:
root.fetch("foo", JoinType.LEFT);
causes this error :
java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [foo] on this ManagedType ...
how can I fetch fields from subclasses?
update:
using treat does not resolve my problem:
Root<Bus> busRoot = builder.treat(root, Bus.class);
busRoot.fetch("foo", JoinType.INNER);
I don't receive any error but foo does not fetch yet.
generated SQL is:
SELECT vehicle0_.VEHICLE_ID AS VEHICLE_2_72_,
vehicle0_.ATTACHMENT_COUNT AS ATTACHME3_72_,
vehicle0_.COMMENTS AS COMMENTS4_72_,
vehicle0_.CREATE_TIMESTAMP AS CREATE_T5_72_,
vehicle0_.CREATOR_USER_ID AS CREATOR_8_72_,
vehicle0_.MODIFIER_USER_ID AS MODIFIER9_72_,
vehicle0_.UPDATE_TIMESTAMP AS UPDATE_T6_72_,
vehicle0_.MAX_SPEED AS MAX_SPEE7_72_,
vehicle0_1_.FOO_OF_VEHICLE AS FOO_OF_V3_70_,
vehicle0_1_.PASSENGER_NUMBER AS PASSENGE1_70_,
vehicle0_2_.ENGINE_TYPE AS ENGINE_T1_71_,
vehicle0_.VEHICLE_TYPE_ID AS VEHICLE_1_72_
FROM TEST_VEHICLE vehicle0_
LEFT OUTER JOIN TEST_BUS vehicle0_1_
ON vehicle0_.VEHICLE_ID=vehicle0_1_.VEHICLE_ID
LEFT OUTER JOIN TEST_CAR vehicle0_2_
ON vehicle0_.VEHICLE_ID =vehicle0_2_.VEHICLE_ID
WHERE vehicle0_.VEHICLE_ID=105
This problem can be solved using meta model.
public abstract class Bus_ extends com.rh.cores.architecture.tests.models.Vehicle_ {
public static volatile SingularAttribute<Bus, Foo> foo;
public static volatile SingularAttribute<Bus, Short> passengerNumber;
}
means this:
root.fetch(Bus_.foo, JoinType.LEFT);
but since fetch signature in JPA is like this:
<Y> Fetch<X, Y> fetch(SingularAttribute<? super X, Y> attribute, JoinType jt);
above code causes compile error!
with changing code like this:
SingularAttribute attribute = Bus_.foo;
root.fetch(attribute, JoinType.LEFT);
we can bypass generics check SingularAttribute<? super X, Y> in JPA standard while Hibernate handle it!
I am trying to create a dynamic query with Specification with two entities which have bidirectional relation. The entities are:
#Entity
#Table("SUPPLIERS")
public class Supplier implements Serializable {
#Id
Column("ID")
private Long id;
#Id
Column("COMPANY_ID")
private Long companyId;
}
#Entity
#Table("EMPLOYEES")
public class Employee implements Serializable {
#Id
private Long id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "FIRM_ID", referencedColumnName = "ID"),
#JoinColumn(name = "FIRM_COMPANY_ID", referencedColumnName = "COMPANY_ID")
})
private Supplier supplier;
}
When I want to select employees based on their supplier,
return new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Long[] supplierCodes = {1L, 2L};
Subquery<Supplier> supplierBasicSubquery = query.subquery(Supplier.class);
Root<Supplier> supplierBasicRoot = supplierBasicSubquery.from(Supplier.class);
Join<Employee, Supplier> sqTfV = root.join("supplier", JoinType.INNER);
supplierBasicSubquery.select(sqTfV).where(sqTfV.<Long>get("id").in(supplierCodes));
return root.<Supplier>get("supplier").in(supplierBasicSubquery);
}
};
When its executed, it generates SQL like:
SELECT ....
FROM EMPLOYEES E
INNER JOIN ....
WHERE (E.FIRM_ID, E.FIRM_COMPANY_ID) in
(SELECT (s.ID, s.COMPANY_ID) FROM SUPPLIERS WHERE SUPPLIER.ID in (1, 2))
As you can see, the inner select columns are surrounded by parenthesis which causes Oracle to throw exception:
java.sql.SQLSyntaxErrorException: ORA-00920: invalid relational operator
How can I fix this issue, any suggestions?
Thanks a lot.
I have a simple model in my project.
[UpdatePackage] >- (ManyToOne) - [Version] -< [UseCase] - (ManyToMany)
public class UpdatePackage implements Comparable<UpdatePackage> {
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = COLUMN_ORIG_VERSION, nullable = true)
private Version origVersion;
// setters and getters
}
#Entity
#Table(name = Version.TABLE_NAME)
public class Version {
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = JVUC_TABLE, joinColumns = { #JoinColumn(name = JVUC_COLUMN_VERSION, referencedColumnName = COLUMN_ID) }, inverseJoinColumns = { #JoinColumn(name = JVUC_COLUMN_USECASE, referencedColumnName = UseCase.COLUMN_ID) })
private final Set<UseCase> useCases = new HashSet<UseCase>();
// setters and getters
}
#Entity
#Table(name = UseCase.TABLE_NAME)
public class UseCase {
#Column(name = COLUMN_NAME, nullable = false)
private String name;
// setters and getters
}
For implementation of filter I would like to use Spring Data Jpa and Specification from spring.data.jpa.domain
For instance I would like to find list of UpdatePackage with given usecase names.
I understand that for ManyToOne relation I need use Join and for ManyToMany I need to use Fetch.
My implementation of Specification interface looks like this:
public static Specification<UpdatePackage> useCaseNames(final List<String> useCaseNames) {
return new Specification<UpdatePackage>() {
#Override
public Predicate toPredicate(Root<UpdatePackage> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
final Join<UpdatePackage, Version> version = root.join(UpdatePackage_.destVersion,
JoinType.LEFT);
Fetch<Version, UseCase> useCase = version.fetch(Version_.useCases, JoinType.LEFT);
return null;
// return useCase.get(UseCase_.name).in(useCaseNames);
}
};
}
When I run a integration test I got NPException in line:
Fetch<Version, UseCase> useCase = version.fetch(Version_.useCases, JoinType.LEFT);
because fields joins and fetches of object version are null.
I don't know what I do in wrong way and I cannot find any answer in Internet.
Does anyone know what is wrong in this code?
Stack:
java.lang.NullPointerException
at org.hibernate.ejb.criteria.path.AbstractFromImpl.constructJoin(AbstractFromImpl.java:261)
at org.hibernate.ejb.criteria.path.AbstractFromImpl.fetch(AbstractFromImpl.java:549)
I've found a solution. I had a bug in static model.
static model
From:
public static volatile SingularAttribute<Version, UseCase> useCases;
To:
public static volatile SetAttribute<Version, UseCase> useCases;
and in implementation of specification:
public static Specification<UpdatePackage> useCaseNames(final List<String> useCaseNames) {
return new Specification<UpdatePackage>() {
#Override
public Predicate toPredicate(Root<UpdatePackage> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
final Join<UpdatePackage, Version> version = root.join(UpdatePackage_.destVersion,
JoinType.LEFT);
final Join<Version, UseCase> useCase = version.join(Version_.useCases);
return useCase.get(UseCase_.name).in(useCaseNames);
}
};
}
I have the following entities:
#Entity
#Table(name = "place_revision")
public class PoiRevision {
#OneToMany(mappedBy = "pk.revision", cascade = {CascadeType.ALL})
private Collection<PoiRevisionCategory> categoryMapping;
// ...
}
#Entity
#Table(name = "place_revision__category")
#AssociationOverrides({
#AssociationOverride(name = "pk.revision",
joinColumns = #JoinColumn(name = "place_revision_id")),
#AssociationOverride(name = "pk.category",
joinColumns = #JoinColumn(name = "category_id"))
})
public class PoiRevisionCategory {
#EmbeddedId
private PoiRevisionCategoryId pk = new PoiRevisionCategoryId();
// ...
}
#Embeddable
public class PoiRevisionCategoryId implements Serializable {
#ManyToOne
private PoiRevision revision;
#ManyToOne
private Category category;
// ...
}
#Entity
#Table(name = "category")
public class Category {
#ManyToMany(targetEntity = Section.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(
name = "category__section",
joinColumns = #JoinColumn(name = "category_id"),
inverseJoinColumns = #JoinColumn(name = "section_id")
)
private Collection<Section> sections;
// ...
}
And want to select PoiRevisions that have Categories that have some Sections.
I'm using Spring-data Specification to query the database for these entities.
My intent is to write something like:
Specification<PoiRevision> spec = new Specification<PoiRevision>() {
#Override
public Predicate toPredicate(Root<PoiRevision> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> conditions = new ArrayList<>(CONDITION_COUNT);
CollectionJoin<PoiRevision, PoiRevisionCategory> mapping = root.join(PoiRevision_.categoryMapping);
// BROKEN here as we cannot use nested path for joins
Join<PoiRevisionCategory, Category> categories = mapping.join("pk.category");
conditions.add(categories.get("sections").in(sections));
// ...
return cb.and(conditions.toArray(new Predicate[] {}));
}
};
But we cannot use nested path for such joins as JPA provider (Hibernate, in my case) looks only for direct properties of PoiRevisionCategory class. And we cannot "join" embedded Id to our result set because it's not a manageable entity.
I'm really stuck with this issue which seems to be far from complicated when translated into SQL yet it has some complexity on the ORM-side.
Any idea is much appreciated.
After switching completely to metamodel API it became clearer and I was actually able to join embedded entity just like I tried and failed with string api.
So the correct way is just to join like one would normally do
Join<PoiRevisionCategory, PoiRevisionCategoryId> pk = mapping.join(PoiRevisionCategory_.pk);
Join<PoiRevisionCategoryId, Category> cats = pk.join(PoiRevisionCategoryId_.category);
CollectionJoin<Category, Section> sec = cats.join(Category_.sections);
conditions.add(sec.get(Section_.id).in(sections));
And it does the thing just fine!
What a relief.