I would like to use the
#QueryHint(name=QueryHints.BATCH, value="pi.jrnls")
annotation together with the primary key #Id lookups.
Where do I put the annotation? It looks like I can only put it inside a #NamedQuery. But the default pk lookup does not have a #NamedQuery of course.
#Entity
#Table(schema="prd", name="PRDITEM", uniqueConstraints= {#UniqueConstraint(columnNames= {"prditmNO"})})
#Cache(expiry=com.quoka.qis.lib.persistence.Constants.SHORT_CACHE_PERIODE_MS)
#NamedQuery(name = "PrdItem.findByNo", query = "select pi from PrdItem pi where pi.no = ?1",
hints={
#QueryHint(name=QueryHints.BATCH, value="pi.jrnls"),
#QueryHint(name=QueryHints.BATCH, value="pi.bookings")//,
}
)
public class PrdItem {....
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="prditmID", insertable = false, nullable=false, unique=true)
private Long id;
EclipseLink has a BatchFetch annotation you can add to the mapping:
http://eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_batchfetch.htm
JPA 2.0 also has a find signature that takes in the class, key and properties map.
Related
Question
If I have declared my (composite) primary key using #IdClass, how do I write my #Query to be able to issue a DELETE query using a Collection<MyIdClass> ?
Secondary question
Will the CASCADE actually trigger the deletion of the associated AnotherEntity despite using #Query?
Current model
#Entity
#Table(name = "myentity")
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#IdClass(MyIdClass.class)
public class MyEntity {
#Id
#Column(updatable = false)
private String foo;
#Id
#Column(updatable = false)
private String bar;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "my_foreign_key", referencedColumnName = "external_pk")
private AnotherEntity anotherEntity;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class MyIdClass implements Serializable {
private String foo;
private String bar;
}
#Entity
#Table(name = "anotherentity")
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class AnotherEntity {
#Id
#Column(name = "external_pk", nullable = false, updatable = false)
private String externalPk;
}
What I've read
A few resources:
https://www.baeldung.com/spring-data-jpa-query
https://www.baeldung.com/spring-data-jpa-delete
https://stackoverflow.com/a/36765129/9768291
And I also found this SO question which seemed very close to what I'm looking for, but unfortunately there are no answers.
Goal
Something similar to:
#Repository
public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> {
#Modifying
#Query("DELETE FROM myentity m WHERE m IN ?1") // how do I write this?
void deleteAllWithIds(Collection<MyIdClass> ids);
}
Ultimately, I want to do this to batch my DELETE requests to increase the performance.
Pitfalls I'm trying to avoid
I know there is a deleteAll(Iterable<? extends MyEntity>) but then I need to actually have those entities to begin with, which would require extra calls to the DB.
There is also deleteById(MyIdClass), but that actually always issues a findById before sending a single DELETE statement as a transaction: not good for the performance!
Potentially irrelevant precision
I'm not sure if that can help, but my JPA provider is EclipseLink. My understanding is that there are properties for batching requests, and that's ultimately what I'm aiming to use.
However, I'm not entirely sure what are the internal requirements for that batching to happen. For example, if I did a deleteById in a for-loop, would the alternating SELECT and DELETE statements prevent the batching from happening? The documentation is quite scarce about that.
If you're positive IdClass is a better choice than EmbeddedId in your situation, you could add an extra mapping to MyEntity :
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "foo",
column = #Column(name = "foo", insertable = false, updatable = false)),
#AttributeOverride(name = "bar",
column = #Column(name = "bar", insertable = false, updatable = false))})
private MyIdClass id;
and use it in you repository:
#Modifying
#Query("DELETE FROM MyEntity me WHERE me.id in (:ids)")
void deleteByIdIn(#Param("ids") Collection<MyIdClass> ids);
This will generate a single query: delete from myentity where bar=? and foo=? [or bar=? and foo=?]..., resulting in this test to pass (with following table records insert into myentity(foo,bar) values ('foo1', 'bar1'),('foo2', 'bar2'),('foo3', 'bar3'),('foo4', 'bar4');):
#Test
#Transactional
void deleteByInWithQuery_multipleIds_allDeleted() {
assertEquals(4, ((Collection<MyEntity>) myEntityRepository.findAll()).size());
MyIdClass id1 = new MyIdClass("foo1", "bar1");
MyIdClass id2 = new MyIdClass("foo2", "bar2");
assertDoesNotThrow(() -> myEntityRepository.deleteByIdIn(List.of(id1, id2)));
assertEquals(2, ((Collection<MyEntity>) myEntityRepository.findAll()).size());
}
I think you are looking for something that will generate a query like this
delete from myentity where MyIdClass in (? , ? , ?)
You can try from this post, it may help you.
This answer provided great insight, but it seems like the approach only works for Hibernate. EclipseLink, which is the JPA Provider that I'm forced to use, would keep throwing an error at me, for the same code.
The only working solution I found is the following hack:
JPA Query for Spring #Repository
#Repository
public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> {
#Modifying
#Query("DELETE FROM myentity m WHERE CONCAT(m.foo, '~', m.bar) IN :ids")
void deleteAllWithConcatenatedIds(#Param("ids") Collection<String> ids);
}
Associated index for the DB (Postgres)
DROP INDEX IF EXISTS concatenated_pk_index;
CREATE UNIQUE INDEX concatenated_pk_index ON myentity USING btree (( foo || '~' || bar ));
Explanation
Since EclipseLink refuses to properly treat my #IdClass, I had to adapt the service to concatenate the composite key into a single String. Then, in Postgres, you can actually create an index on that concatenation of different composite key columns.
Labeling the index as UNIQUE will greatly improve the performance of that query, but should only be done if you are sure that the concatenation will be unique (in my case it is since I'm using all the columns of the composite key).
The calling service then only has to do something like String.join("~", dto.getFoo(), dto.getBar()) and to collect all of those into the list that will be passed to the repository.
I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee
I have two tables, Projects and TransitionAction. Both Projects and TransitionAction have a column request_no which is used perform join between them. The entity classes are as below:-
Project.java
#Entity
public class Project implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(schema = "public", name="project_id_seq_gen",sequenceName="project_id_seq",initialValue=1,allocationSize=1)
#GeneratedValue(strategy= GenerationType.SEQUENCE,generator="project_id_seq_gen")
private Integer id;
#Column(name = "request_no")
private String request_no;
#Column(name = "title")
private String title;
#Column(name = "department")
private String department;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "requestNo")
private Set<TransitionAction> tacts;
#ManyToOne
#JoinColumn(name = "status_id")
private Status status;
#ManyToOne
#JoinColumn(name = "level_id")
private ProjectLevel level;
TransitionAction.java
#Entity
#Table(name = "transitionaction")
public class TransitionAction implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "request_no")
private String request_no;
#Column(name = "actionDate")
private Date actionDate;
The code used to retrieve the list of projects as follows:-
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
UserInfo findByUserName(String userName);
#Query("SELECT project FROM Project project Join project.tacts pta where project.request_no= pta.request_no and project.status.id=1")
List<Project> getAllUserProjects();
}
I am getting could not extract ResultSet error. When I checked console, i found the following query being generated:
select
distinct project0_.id as id1_1_,
project0_.department as departme2_1_,
project0_.level_id as level_id6_1_,
project0_.user_nodal_officer as user_nod3_1_,
project0_.request_no as request_4_1_,
project0_.status_id as status_i7_1_,
project0_.title as title5_1_
from
project project0_
inner join
transitionaction tacts1_
on project0_.id=tacts1_.request_no
I am not getting why project. id is joined with tact.request_no and creating the error
Operator does not exist: integer = character varying
So you want to get all TransitionAction linked to Project by requestNo
You can achieve this by
adding #ManyToOne Mapping in TransactionAction like this
#ManyToOne(fetch = FetchType.LAZY)
private Project project;
Now you need to modify your query like this
#Query("SELECT project FROM Project project where project.status.id=1")
List<Project> getAllUserProjects();
To get all TransactionAction for a given project
Set<TransitionAction> allTatcts = project.getTacts();
You dont need to add join in query. Hibernate will take care of that once you pull TransactionAction from Project by an entity.
Edit 1:
But why did my query fail ? Why primary key of project joined with
request_no of TransitionAction ?
Your query failed because while doing #OneTOMany relation yodidn'tnt define #JoinColumn or #JoinTable which is a unidirectional mapping.
In this case, Hibernate will use primarykey column to map.
And since type of primarykey and column is different thus the error.
Without describing any physical mapping (no #JoinColumn or
#JoinTable), a unidirectional one to many with join table is used. The
table name is the concatenation of the owner table name, _, and the
other side table name. The foreign key name(s) referencing the owner
table is the concatenation of the owner table, _, and the owner
primary key column(s) name. The foreign key name(s) referencing the
other side is the concatenation of the owner property name, _, and the
other side primary key column(s) name. A unique constraint is added to
the foreign key referencing the other side table to reflect the one to
many.
Refer Official doc for more detail
#OneToMany
#JoinTable(
name="table_name",
joinColumns = #JoinColumn( name="columnname")
)
private Set<TransitionAction> tacts;
It is because you are having #Id on request_no in your TransactionAction class and when you join two class then they join on primary keys.
You can use cross join instead.
SELECT project FROM Project project, TransactionAction pta where project.request_no= pta.request_no and project.status.id=1
Or you can have bidirectional mapping. Add the following in your TransactionAction entity class.
#ManyToOne
private Project Project;
And then your query will be as follows.
select pta.project from TransactionAction pta where pta.request_no=pta.project.request_no and pta.project.status.id=1
I have two entities :
RawDeviceMessage which represents a raw message from a device
TagDetail which represents the message after being parsed
A TagDetail may or may not be associated with a RawDeviceMessage, because it may be created directly without a raw message to parse. Thus, I have a optional bi-directional OneToOne relation between RawDeviceMessage and TagDetail.
In the database I have the following tables :
raw_device_message (id + other columns)
tag_detail (id + other columns)
tag_detail_has_raw_device_message (tag_detail_id , raw_device_message_id) : this table is a JoinTable with the proper SQL constraints and foreign keys to enforce the OneToOne relation at the database level.
I have mapped my Java classes like that :
RawDeviceMessage
#Entity
#Table(name = "raw_device_message")
public class RawDeviceMessage implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#OneToOne(mappedBy = "rawDeviceMessage", fetch = FetchType.LAZY)
private TagDetail tagDetail;
public RawDeviceMessage(){}
public Long getId(){...}
public void setId(final Long id){...}
public TagDetail getTagDetail(){...}
public RawDeviceMessage setTagDetail(TagDetail tagDetail){...}
}
TagDetail
#Entity
#Table(name = "tag_detail")
public class TagDetail implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.REFRESH, CascadeType.MERGE })
#JoinTable(
name="tag_detail_has_raw_device_message",
joinColumns=#JoinColumn(name="tag_detail_id"),
inverseJoinColumns=#JoinColumn(name="raw_device_message_id"))
private RawDeviceMessage rawDeviceMessage;
public TagDetail() {}
public Long getId(){...}
public void setId(final Long id){...}
public RawDeviceMessage getRawDeviceMessage(){...}
public void setRawDeviceMessage(RawDeviceMessage rawDeviceMessage){...}
}
The issue
My issue is that when performing a find all on the RawDeviceMessage resource, Hibernate generates the wrong SQL query :
SELECT rawdevicem0_.id AS id1_15_,
rawdevicem0_2_.tag_detail_id AS tag_deta0_37_,
FROM raw_device_message rawdevicem0_
LEFT OUTER JOIN tag_detail_has_raw_device_message rawdevicem0_2_ ON rawdevicem0_.id=rawdevicem0_2_.tag_detail_id
CROSS JOIN tag_detail tagdetail1_
LEFT OUTER JOIN tag_detail_has_raw_device_message tagdetail1_1_ ON tagdetail1_.id=tagdetail1_1_.tag_detail_id
WHERE rawdevicem0_2_.tag_detail_id=tagdetail1_.id
ORDER BY rawdevicem0_.id ASC
As you can see, in the first LEFT OUTER JOIN, the join condition is rawdevicem0_.id=rawdevicem0_2_.tag_detail_id
It tries to join raw_device_message.id with tag_detail_has_raw_device_message.tag_detail_id , which makes no sense and messes up with all the results.
Instead the join condition should be, rawdevicem0_.id=rawdevicem0_2_.raw_device_message_id
This condition would correctly join raw_device_message.id with tag_detail_has_raw_device_message.raw_device_message_id
I have shortened the query generated by hibernate to remove all unrelated fields, but in the generated query there is nowhere the column raw_device_message_id, so there is definitely something wrong.
Is it an hibernate bug or am I doing my mapping wrong ?
If the purpose of tag_detail_has_raw_device_message table is only to link the two tables, then you can drop it. You can have One-to-One with just the two tables.
More details here -
Setting up a One To ManyJoins Against a Bridge Table using JPA
However if you want to have an intermediate mapping table, because it has some additional info for that relationship, then more details here.
http://what-when-how.com/hibernate/advanced-entity-association-mappings-hibernate/
I've got a question about Spring JPA (JPQL and sorts).
Been searching my ass of for this and i can't seem to find an answer.
If there is any duplicate, please let me know. I haven't come across a question/tutorial/guide that fits my needs.
Okay so i got Spring with JPA. And i have 2 entities.
Asset and AssetStatus. An Asset could have multiple AssetStatuses, one AssetStatus belongs to an Asset.
The entities are build as following. I omitted the unneeded properties.
Asset:
#Entity
#Table(name = "T_ASSET")
public class Asset implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "asset", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<AssetStatus> assetStatus;
}
AssetStatus:
#Entity
#Table(name = "T_ASSETSTATUS")
public class AssetStatus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
#JsonSerialize(using = CustomLocalDateTimeSerializer.class)
#JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
#Column(name = "timestamp", nullable = false, insertable = true, updatable = true)
private LocalDateTime timestamp;
#JsonIgnore
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Asset asset;
}
And i got a standard AssetRepository. (Ignore the find by barcode, its an omitted property from Asset.)
public interface AssetRepository extends JpaRepository<Asset, Long>, QueryDslPredicateExecutor<Asset> {
Asset findOneByBarcode(String barcode);
}
Now i want to get all my assets, with the last AssetStatus they had. Which is defined by the timestamp in AssetStatus. Getting all assets works but i also get all AssetStatuses they have (or had). I just want the most recent.
How do i manage to do this? I tried writing a custom query with #Query. Or name a method like findLastAssetStatus() or something. All didn't work.
Is there someone who could help me figure this out? I am willing to give more information and answer any questions asked.
EDIT: I found out the query in MySQL to get the wanted result (Works in MySQL Workbench on my database):
SELECT * FROM T_ASSET asset JOIN T_ASSETSTATUS assetStatus WHERE asset.id = assetStatus.asset_id AND assetStatus.timestamp = (SELECT max(timestamp) FROM T_ASSETSTATUS WHERE asset_id = asset.id);
As i use this in my AssetRepository with #query nativequery=true (As specified below), it does not give me the wanted result. This will still give me every status with every assetStatus it has ever had.
#Query(value = "SELECT * FROM T_ASSET asset JOIN T_ASSETSTATUS assetStatus WHERE asset.id = assetStatus.asset_id AND assetStatus.timestamp = (SELECT max(timestamp) FROM T_ASSETSTATUS WHERE asset_id = asset.id)", nativeQuery = true)
List<Asset> findAssetWithlastStatus();
How do i write this query in JPQL? Or solve this in any other way?
Thanks in advance!
Cheers, Clemenz
You can take a look at Hibernate filters. Write a filter which will filter out the old statuses and enable it in the sessions in which you want it to be applied.