Hibernate join using non-primary key column - java

I have two tables:
users:
user_id (primary)
ip (unique)
etc ..
services_to_ip
id (primary)
service_id
ip
etc ..
In class User:
#OneToMany()
#JoinColumn(name = "ip", insertable = false, nullable = false, updatable = false)
public List<QPlanService> getPlanServices() {
return planServices;
}
Using MySQL query log we get something like that:
SELECT *
FROM services_users planservic0_
LEFT OUTER JOIN services qservice1_
ON planservic0_.service_id = qservice1_.id
WHERE planservic0_.ip = 777
In WHERE condition the 'user_id' field used (the default field is primary key - users.id) (user_id=777).
How can I specify that I need to take the value of the 'ip' field from User entity, not 'user_id'?
I will be grateful for any help!

JoinColumn will only specify the name of the column holding the foreign key, so changing the name of joincolumn absolutely will not help.
Hibernate will be using the primary key by default for joining, if you want to override this you can simply use referencedColumnName in your relation, but the referenced column should be unique

As Amer Qarabsa mentioned above:
#OneToMany()
#JoinColumn(name = "ip", insertable = false, nullable = false, updatable = false, referencedColumnName="ipcolumnName")
public List<QPlanService> getPlanServices() {
return planServices;
}

Related

How to prevent JPA/Hibernate attempts to delete from (read-only) DB view mapped Map property?

I have something like this:
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "my_read_only_view",
joinColumns = {#JoinColumn(name = "entity_id", referencedColumnName = "id", updatable = false, insertable = false)})
#MapKeyColumn(name = "key", updatable = false, insertable = false)
#Column(name = "value", updatable = false, insertable = false)
private Map<Long, MyEnum> mapProperty;
As its name says my_read_only_view is a read-only database view that "calculates" keys and values automagically using a DB-specific SQL (and I cannot accomplish the same in JPA/Hibernate scope or QLs). However, when the "owner" entity is deleted, Hibernate tries to do what it believes is its duty... and delete the relevant row from that read-only view. The DB then responds with cannot delete from view error.
Additional details (that I cannot change):
Hibernate 5.4.14
Using field access + bytecode enhancement: enableLazyInitialization = true, enableDirtyTracking = true, enableAssociationManagement = false, enableExtendedEnhancement = false ... and hibernate.bytecode.use_reflection_optimizer = true
That field is literally only used by a getter. In fact, not even that getter is used. This mapping exists solely so that we can include it in search criteria, isn't even supposed to ever be fetched.
I've dealt with the error by creating a DB rule to ignore attempts to delete... but that still leaves some performance impact. What is the best way to stop Hibernate from attempting to delete this? I was looking at #Formula but this is a map and it is said that Hibernate will execute it for every fetch, which is definitely not what we need or want.

How to retrieve the newest(current) time value specificd by DEFAULT CURRENT_TIMESTAMP on jpa entity's property?

I have a JPA entity, which contains two column, the createdTime and modifiedTime. I'd like to use MySQL's DEFAULT CURRENT_TIMESTAMP feature to generate the proper time value for this two column, so i write my entity class like:
#Entity
public class Blog {
// ...
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP")
private Date createdTime;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
private Date modifiedTime;
}
Then i do some experiment on my application, when i check the database, the value of this two column in the database is updated properly.
However, when i check the java entity return by BlogRepository::save, the value of this two column is not the newest value. For example, when i insert a new Blog entity, i get null for this two properties on the BlogRepository::save return value.
I've read some post like https://stackoverflow.com/a/12237110/8510613, understand that if i mark the column as updatable = false, this situation will happen. So how should i fix this? Simply remove the updatable = false/insertable = false seems have no help, but will trigger the column's not null constraint violation. But if i remove the not null constraint on this two column, the value of this two column are just simply null.
You should use #Generated annotation in this case.
2.3.18. Generated properties
Generated properties are properties that have their values generated by the database. Typically, Hibernate applications needed to refresh objects that contain any properties for which the database was generating values. Marking properties as generated, however, lets the application delegate this responsibility to Hibernate. When Hibernate issues an SQL INSERT or UPDATE for an entity that has defined generated properties, it immediately issues a select to retrieve the generated values.
So, you should correct your mapping something like this:
#Temporal(TemporalType.TIMESTAMP)
#Generated(value = GenerationTime.INSERT)
#Column(name = "created_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP")
private Date createdTime;
#Temporal(TemporalType.TIMESTAMP)
#Generated(value = GenerationTime.ALWAYS)
#Column(name = "modified_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
private Date modifiedTime;

Hibernate sql annotation

I am using Hibernate to interface with SQL Server 2016/Azure SQL Server currently, and have been having a great time with it so far. In my database, I have implemented system versioned temporal tables. I want to map (preferably lazily) two more variables by annotation only to my Hibernate entity that represent the original ValidFrom and UpdatedBy fields from the temporal history of the appropriate table.
For example, I have a class and table for Accounts. The Account [minus nonrelated columns, constraints, etc] table is as follows:
CREATE TABLE [dbo].[Account] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[UpdatedBy] INT NOT NULL,
[ValidFrom] DATETIME2 (7) GENERATED ALWAYS AS ROW START DEFAULT (sysutcdatetime()) NOT NULL,
[ValidTo] DATETIME2 (7) GENERATED ALWAYS AS ROW END DEFAULT (CONVERT([datetime2],'9999-12-31 23:59:59.9999999')) NOT NULL,
CONSTRAINT [FK_Account.UpdatedById_Account.Id] FOREIGN KEY ([UpdatedBy]) REFERENCES [dbo].[Account] ([Id]),
PRIMARY KEY CLUSTERED ([Id] ASC),
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[AccountHistory], DATA_CONSISTENCY_CHECK=ON));
The SQL statement to get the data that I want looks like this (I imagine that I would select only UpdatedBy or ValidFrom per annotation, but they are together now to be concise):
SELECT UpdatedBy, ValidFrom FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ValidFrom IN
(
SELECT MIN(ValidFrom) OVER (Partition BY Id) AS ValidFrom
FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ID = $(passedInIdOfThisEntity)
)
Finally, my Hibernate entity/pojo looks something like this (again, redacting irrelevant variables):
#Entity
#Table(name = "Account")
public class Account implements Serializable {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "UpdatedBy")
private Account updatedBy;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", nullable = false, length = 27, insertable = false, updatable = false)
private Date validFrom;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidTo", nullable = false, length = 27, insertable = false, updatable = false)
private Date validTo;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "updatedBy")
private Set<Account> accountsUpdated;
// This is a stub of what I'm hoping you can help me add
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Date createdOn;
#Column(name = "UpdatedBy", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Account createdBy
// ... getters and setters below
}
I have been using Hibernate to a great extent, but have had trouble finding information on this, though I have found and used examples of implementing native queries for retrieving entities instead of using criteria queries. If you can help me solve this riddle to allow me to continue using criteria queries to retrieve data and populate these fields through annotation on demand, I would greatly appreciate it.
The temporal table constructs that you've described isn't something that I am aware that JPA or even Hibernate support natively. These are likely new features of the ANSI SQL standard which haven't made their way into proper support.
That said, that doesn't mean you cannot use frameworks like Hibernate to accomplish the task. As indicated in the comments, you can specify a named query and execute that in order to get the attributes you desire.
From a JPA 2.1 perspective, you use #SqlResultSetMapping and #ConstructorResult.
#SqlResultSetMapping(
name = "Account.getWithTemporalAttributes",
classes = {
#ConstructorResult(
targetClass = com.company.domain.AccountTemporalDetails.class,
columns = {
#ColumnResult(name = "col1"),
#ColumnResult(name = "col2")
})
})
To use this, you would do the following:
Query query = entityManager.createNativeQuery(
"SELECT a.col1 as col1, a.col2 as col2 FROM Account a",
"Account.getWithTemporalAttributes");
List<AccountTemporalDetails> results = query.getResultList();
That should allow you to use Native SQL queries, mapping them to a POJO which you can easily then use within your application without having to write boilerplate. The #ConstructorResult annotation is meant to mimic the JPQL SELECT NEW syntax. So you would just need to make sure that AccountTemporalDetails had a constructor that takes those arguments with the right types.

When use the JPA innerJoin, How can i change 'and' to 'or'?

When mapping two or more columns in innerjoin there is a map to the conditions and
Can I change or a condition?
Parent table
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private List<KeyboxDept> keyboxDept;
Child table
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "KEY_TARGET_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false),
#JoinColumn(name = "KEY_TARGET_UPPER_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false)
})
private User user;
Query is made
select
user0_.user_id as user_id1_3_,
user0_.dept_id as dept_id2_3_,
user0_.posi_id as posi_id3_3_
from
cm_user user0_
inner join
cm_keybox_dept keyboxdept2_
on user0_.dept_id=keyboxdept2_.key_target_id
and user0_.dept_id=keyboxdept2_.key_target_upper_id
where
user0_.user_id=? limit ?
Can i switch and -> or ???
I am not sure about JPA but In Hibernate Using Criteria we can do this....
Criteria criteria = session.createCriteria(persistentClass);
criteria.createCriteria("propertyName", Criteria.LEFT_JOIN);
You have cascade = CascadeType.ALL in entity definition. That means, The operations that must be cascaded to the target of the association. So the target (KeyboxDept in your code) must exist. If you remove it, means no operations being cascaded, I think the generated sql will have no inner.
If your search criteria is not a foreign key matching to the joined table key then you cannot use this syntax because there is obviously an AND relationship between the separate column parts (#JoinColumn elements) of the foreign key. This is why the generated native SQL contains the AND. If you need the OR, you need to initialize that member with a separate JPQL query which contains the OR and you do not need the annotation #ManyToOne.

#ManyToOne, Can one column reference to two other Columns?

Can I have a relationship as below:
#Entity Table1{
#ManyToOne
#JoinColumn(name = "Column1",
referencedColumnName = "t2id",
insertable = false,
updatable = false)
private Table2 table2_col;
#ManyToOne
#JoinColumn(name = "Column1",
referencedColumnName = "t3id",
insertable = false,
updatable = false)
private Table3 table3_col;
}
Yes, the mapping looks valid.
The column Column1 in both cases belong to different tables (Table2.column1 and Table3.column1). So I don't see any collision here. It is not the case as the tittle says "one column references to two other columns".
In this case you have two many-to-one relations: Table1<--->Table2 and Table1<--->Table3. So the column1 in both tables (2 and 3) is a Foreign Key to the Table1. So you have 2 different foreign keys.

Categories