I have Car and Color classes. Car class has both Entity and Id fields of Color.
Here is the Car class code:
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(name = "color_id", insertable = false, updatable = false)
#ManyToOne(targetEntity = CarColor.class, fetch = FetchType.EAGER)
private CarColor color;
#Column(name = "color_id")
private Long colorId;
}
Here is the Color class code:
public class CarColor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#JsonIgnore
#OneToMany(mappedBy = "color")
List<Car> cars;
}
When I'm fetching a Car i have an extra query to get a color Entity which is causing n+1 problem. So I have tried to use joins.
return query.select(qCar)
.from(qCar)
.innerJoin(qCar.color(), QCarColor.carColor)
.where(predicate)
.fetch();
I'm getting these queries:
select
car0_.id as id1_3_,
car0_.color_id as color_id3_3_,
from
car car0_
inner join
color carcolor2_
on car0_.color_id=carcolor2_.id
Hibernate:
select
carcolor0_.id as id1_6_0_,
carcolor0_.name as name2_6_0_
from
color carcolor0_
where
carcolor0_.id=?
As you can see, at the end I have an extra query to get color even though it has already joined Color entity. That is the main problem. Maybe it somehow connected with my decision to store both Entity and Id.
Queries don't use the association eagerness to add joins arbitrarily to your query. After that the entity loader still notices that this is an eager association and will fetch it separately.
You can however simply add the join yourself. Important here is to add the join as fetch join, so that the join result will be initialised as the value of the association.
return query.select(qCar)
.from(qCar)
.innerJoin(qCar.color(), QCarColor.carColor).fetchJoin()
.where(predicate)
.fetch();
Related
I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.
I'm trying to perform a query to find cars by their foo property. The properties are stored in a different table.
I have two classes
#Embeddable
#Table(name = "PROPERTY")
public class Property {
#Column(name = "type", nullable = false)
private String type;
#Column(name = "string_value", nullable = true)
private String stringValue;
...
}
#Entity
#Table(name = "CAR")
public class Car {
#Id
...
private String id;
#ElementCollection(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#CollectionTable(name = "PROPERTY", joinColumns = #JoinColumn(name = "car_ID") )
private Set<Property> properties = new HashSet<Property>();
...
}
I'm trying to perform a query
QueryDsl:
.from(car)
.leftJoin(car.properties, foo)
.on(foo.type.eq("foo"))
.where(predicate)
Resulting HQL:
select
car
from
com....Car car
left join
car.properties as foo with foo.type = :a1
where
...
This doesn't work because of: https://hibernate.atlassian.net/browse/HHH-2772
Before that, it was possible to write HQL:
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner=:owner
Now the HQL is raising an exception:
org.hibernate.hql.ast.InvalidWithClauseException: with clause can only reference columns in the driving table
Workaround is to explicitly use primary key (ownerId):
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner.ownerId=:ownerId
The problem is that I don't have the ownerId, or an owner, since it's an element collection.
If I were to turn the element collection into a #oneToMany #manyToOne, the property could not longer be embeddable and would require an id. This is not an option. (I can't define a composite ID (this is a requirement), and I don't want to add a new column )
What do you recommend?
Is it possible to add the Car or Car Id as a field into an embaddable class?
Can I create the criteria in a different way?
I'm interested in any workaround that doesn't require database changes. (Hibernate changes or ok)
Thank you
I have two tables: Tax and TaxRule. There is one column same in both table i.e TAX_RULE_ID. But don't have any Hibernate mapping like OneToOne or OneToMany. Both table looks like-
TAX
#Id
#Column(name = "TAX_RATE_ID")
private Long taxRateId;
#Column(name = "TAX_RULE_ID")
private Long taxRuleId;
#Column(name = "TAX_TYPE")
private String taxType;
TAX_RULE
#Id
#Column(name = "TAX_RULE_ID")
private Long taxRuleId;
#Column(name = "TAX_CD")
private String TaxCode;
#Column(name = "STATE")
private String state;
I am trying to fetch data on the key i.e TAX_RULE_ID. This column is common in both table.
I have following Hibernate code in which I am joining both table on the TAX_RULE_ID column as follows:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<String[]> cQuery = criteriaBuilder.createQuery(String[].class);
Root<Tax> taxRoot = cQuery.from(Tax.class);
cQuery.multiselect(taxRateRoot.get("taxType"), taxRateRoot.get("taxRate"));
List<Predicate> predicates = new ArrayList<>();
Join<Tax, TaxRule> join = taxRoot.join("taxRuleId");
.....rest of the code.
I am getting following Exception on the join point:
org.hibernate.jpa.criteria.BasicPathUsageException: Cannot join to attribute of basic type
at org.hibernate.jpa.criteria.path.AbstractFromImpl.constructJoin(AbstractFromImpl.java:270)
at org.hibernate.jpa.criteria.path.AbstractFromImpl.join(AbstractFromImpl.java:263)
at org.hibernate.jpa.criteria.path.AbstractFromImpl.join(AbstractFromImpl.java:436)
at com.iclsystems.base.dao.TaxRateDAOImpl.getTaxTypeForApplicableWorkOrderTax(TaxRateDAOImpl.java:31)
at com.iclsystems.base.businessObjects.TaxLookupBOImpl.getTaxTypeForApplicableWorkOrderTax(TaxLookupBOImpl.java:53)
at com.iclsystems.base.businessObjects.TaxLookupBOImpl.getWorkOrderTaxLookup(TaxLookupBOImpl.java:29)
at com.iclsystems.test.eventhandler.base.TaxLookupBOTest.testTaxLookupBO(TaxLookupBOTest.java:52)
You cannot use the #Join annotation for a basic property (e.g., an attribute with a simple #Column mapping). #Join is for associations:
one-to-one
one-to-many
many-to-one
many-to-many
You need to remove this line, as the taxRuleId is already fetched from the database:
Join<Tax, TaxRule> join = taxRoot.join("taxRuleId");
If you want to join the TaxRule table, you need to replace the:
#Column(name = "TAX_RULE_ID")
private Long taxRuleId;
with a many-to-one association:
#ManyToOne
#JoinColumn(name = "TAX_RULE_ID")
private TaxRule raxRule;
I am building a messaging system for my web application using Spring MVC with Spring Data JPA and Hibernate as my JPA provider.
I have five entities: Thread, ThreadParticipant, Participant, Account and Company. Each message thread has at least two participants, one of which is associated with a user (Account entity), and the other is associated with a Company. This constraint is enforced by the application. The database is designed like this to support future features. An example of two participants for a given thread in the database looks as follows:
id account_id company_id
1 44 NULL
2 NULL 123
The row with id=1 is the user, and the row with id=2 is the company. What I want to do is to write an HQL query that extracts all Thread objects for a given account, containing both the user/account participant as well as the company participant. I have tried to use different alias for my joins, like this:
select distinct t
from Thread t
inner join fetch t.threadParticipants user_tp
inner join fetch t.threadParticipants company_tp
inner join fetch user_tp.participant user_p
inner join fetch user_p.account a
inner join fetch company_tp.participant receiver_p
inner join fetch receiver_p.company
where a.id = :accountId
I get the exception cannot simultaneously fetch multiple bags due to the two fetches of t.threadParticipants. If I only do a single join here, the generated SQL simply ignores my additional join and only joins to Participant once, which requires a participant to have both an account and a company associated. With raw SQL, I can do like this, and it works fine:
select *
from thread t
inner join thread_participant user_tp on (user_tp.thread_id = t.id)
inner join thread_participant company_tp on (company_tp.thread_id = t.id)
inner join participant user_p on (user_p.id = user_tp.participant_id)
inner join account a on (a.id = user_p.account_id)
inner join participant company_p on (company_p.id = company_tp.participant_id)
inner join company c on (c.id = company_p.company_id)
where a.id = 123;
If I don't use different alias for the same table (see the below query), the query runs fine, but I only get one of the thread participants returned - the one that is associated with the account.
select distinct t
from Thread t
inner join fetch t.threadParticipants tp
inner join fetch tp.participant p
inner join fetch p.account a
left join fetch p.company
where a.id = :accountId
Is there any way that I can do what I am trying to do with HQL, or do I have to go with using native SQL?
My mapping is as follows:
Thread entity
#Entity
#Table(name = "thread")
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Collection<ThreadParticipant> threadParticipants = new HashSet<>();
// Getters and setters
}
Participant entity
#Entity
#Table(name = "participant")
public class Participant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Account.class, cascade = { CascadeType.PERSIST })
#JoinColumn(name = "account_id")
private Account account;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
// Getters and setters
}
ThreadParticipant entity
#Entity
#Table(name = "thread_participant")
#IdClass(ThreadParticipantPK.class)
public class ThreadParticipant implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "participant_id")
private Participant participant;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Thread.class)
#JoinColumn(name = "thread_id")
private Thread thread;
#Column(name = "last_viewed", nullable = true)
private Date lastViewed;
// Getters and setters
}
ThreadParticipantPK
public class ThreadParticipantPK implements Serializable {
private Thread thread;
private Participant participant;
public ThreadParticipantPK() { }
public ThreadParticipantPK(Thread thread, Participant participant) {
this.thread = thread;
this.participant = participant;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThreadParticipantPK)) return false;
ThreadParticipantPK that = (ThreadParticipantPK) o;
if (!participant.equals(that.participant)) return false;
if (!thread.equals(that.thread)) return false;
return true;
}
#Override
public int hashCode() {
int result = thread.hashCode();
result = 31 * result + participant.hashCode();
return result;
}
// Getters and setters
}
Thank you in advance!
Try changing the type of the threadParticipants collection to Set instead of a Collection:
private Set<ThreadParticipant> threadParticipants;
I have recently started to refactor my project because I had to add an extra column to some of my table. The extra column is an Enum (Pending, or Active).
Because of that change I would need now to refactor ALL my queries to only retrieves a row if the status is ACTIVE.
After some research I found that we can annotate an Entity with the #Where annotation. it works fine where I use it on a simple column but my table look like this:
#Where(clause = 'state='ACTIVE'")
#Entity
public class Place {
#Column(name="id_place")
private String placeId;
#Column(name="name")
private String palceName;
#OneToMany(mappedBy = "place")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'state='ACTIVE'")
#Entity
public class Tag {
#Column(name="id_tag")
private String tagId;
#Column(name="name")
private String tagName;
#OneToMany(mappedBy = "tag")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'poi.state='ACTIVE' AND tag.state='ACTIVE")
#Entity
public class PlaceTag {
#Column(name="id")
private String id;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "place_id")
private Place place;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "tag_id")
private Tag tag;
...
...
}
Now my question would be how can make this statement ONLY return the places and tags that are ACTIVE ?
SELECT pt FROM PlaceTag pt;
Is this possible? Or will I have to write the query Explicitly ?
Thank you
As you already discovered, or simply use cases the #Where clause is just fine, but in your case, you want to filter PlaceTag by the place and tag too, so a joined is required in this situation.
So, you can keep the #Where clause for Place and Tag, while for PlaceTags you need to use a JPQL query:
select pt
from PlaceTag pt
join pt.tag t
join pt.place p
where
t.state='ACTIVE' and p.state='ACTIVE'
At least until #WhereJoinTable annotation is made to work for many-to-one associations too.