JPA insertable and updatable with View and SecondaryTable - java

Thera are oracle view vendor_view and table vendors (vendors table contains only PK with name id for simplicity)
create view vendor_view as
select id as vid, 'YES' as active
from vendors;
Coresponding entity
#Entity
#Table(name = "vendors")
#SecondaryTable(name = "vendor_view", pkJoinColumns = {#PrimaryKeyJoinColumn(name = "vid", referencedColumnName = "id")})
public class Vendor {
#Id
private Long id;
#Column(table = "vendor_view", name = "vid", insertable = false, updatable = false)
private Long vid;
#Column(table = "vendor_view", name = "active", insertable = false, updatable = false)
private String active;
getter and setter....
}
When i try to persist new Vendor entity then face with issue:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into vendor_view (vid) values (?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:238)
.....
Caused by: org.hsqldb.HsqlException: INSERT, UPDATE, DELETE or TRUNCATE not permitted for table or view
at org.hsqldb.error.Error.error(Unknown Source)
JPA Implementation is Hibirnate.
Question is why Hibirnate generate insert query for field that mark as insertable = false, updatable = false ?

As hibernate doesn't know whether the table you are trying to insert is a table or a view until and unless it interacts with the database. So it is runtime exception which can be checked only when Java program interacts with Database.
It is similar to even if table doesn't exist it will make query but on runtime it will throw exception.

You would do that when the responsibility of creating/udpating the related entity in question isn't in the current entity. E.g. you have a Person and an Address. You'd like to add insertable=false, updatable=false to the #OneToMany relationship with the Person entity in the Address entity, simply because it's not the responsibility of the Address entity to create or update a Person. It's the other way round. This is not really a technical, but more a semantic/natural decision.

Related

JPA/ManyToOne/JoinColumn with a database View : how to avoid Foreign Key generation?

I have a #ManyToOne association, the target entity maps a database view.
JPA/Hibernate tries to generate a Foreign Key constraint between the table and the view, which is not possible (so I get an exception at each start of application). How to avoid this ?
#Entity
public class ThirdParty{
#Id
String id=UUID.randomUUID().toString();
String bookId;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("bookId")
#JoinColumn(name="bookId", referencedColumnName = "cid", foreignKey = #ForeignKey(name = "none"))
private XCompany xCompany;
//...
}
I get this exception :
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL
Caused by: java.sql.SQLException: (conn=1266699) Cannot add foreign key constraint
Putting foreignKey = #ForeignKey(name = "none") has no effect.
Any idea ?
The naming used for the foreign key can be reserved words. Did you check this?
For example; The word 'none' is a reserved word for MySQL.
Instead of using the name none, use #ForeignKey(ConstraintMode.NO_CONSTRAINT)

Join non-unique column in spring jpa

I was hopping to find an answer to my probleme on this here forum. My problem is as follows, I have two classes :
#Entity
#Table(name = "a")
public class A implements Serializable{
#Id
private String id = UUID.randomUUID().toString();
#Column(name = "REFERENCE_ID")
private String referenceId;
#Column(name = "VERSION")
private String version;
}
And
#Entity
#Table(name = "b")
public class B{
#Id
private String id = UUID.randomUUID().toString();
#Column(name = "REFERENCE")
private String reference;
#ManyToMany(fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(value =
"(select r from A r where r.reference_id = reference_id order by r.version desc limit 1)",
referencedColumnName = "reference_id")),
#JoinColumnOrFormula(column = #JoinColumn(name = "reference_id",
referencedColumnName = "reference_id", insertable = false))
})
private A referenceId;
}
The thing is reference_id is not a unique key in the b table and was just an indicative value in table A so in order to fetch the entire correspondent row I had to do some filtering with the formula in my join annotation.
When I try to fetch my data I get the following error
[Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessResourceUsageException:
could not extract ResultSet; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not extract
ResultSet] with root cause org.postgresql.util.PSQLException:
ERROR: relation "a" does not exist Position : 309
EDIT
ACtually t works as intended when changing my join formula to
#JoinFormula(value =
"(select r from schema_A r where r.reference_id = reference_id order by r.version desc limit 1)",
referencedColumnName = "reference_id"))
the problem now is that the code is intended to work on multipple envirnments
as for my application.yml it looks a bit like this;
jpa:
database: POSTGRESQL
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
default_schema: schema
jdbc:
lob:
non_contextual_creation: true
time_zone: UTC
Thanks for your responses :)
I agree with Simon's comment. For Postgres (and relational databases in general), the word "table" and "relation" are the same and where the term "relational" comes from. So, when it says "Can't find relation B" it literally means "Can't find a table called B".
You should check your connection settings for the schema to see if those tables have/haven't been defined. If it's not obvious, maybe add/edit the question accordingly with your connection settings & appropriate debugging showing you DO see the relations (tables) there.

Hibernate and Criteria Api generates wrong Join condition

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

Hibernate 5.3.10 one-to-many insertable = false behaves differently from Hibernate 5.2.1

I have two classes one contained within the other. SchoolClass and Student
When persisting them in Hibernate 5.2.1 everything works as expected, but when persisting in Hibernate 5.3.10 I have to remove or set insertable = trueto get the same result otherwise I get exception.
What I'm looking for is a confirmation that the behavior of hibernate has changed. When where and why...
I have not been able to find any documentation about this at all.
jdbc.spi.SqlExceptionHelper - NULL not allowed for column "schoolClassId"; SQL statement:
insert into tStudent (studentId, name) values (null, ?)
org.hibernate.exception.ConstraintViolationException: could not execute statement
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement.
#Entity
#Table(name = "tSchoolClass")
#AutowiringTarget
public class SchoolClass {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "schoolClassId")
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "schoolClassId", nullable = false, insertable = false, updatable = false)
private List<Student> students;
#Entity
#Table(name = "tStudents")
#AutowiringTarget
public class Students {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "StudentId")
private Long id;
H2 database.
CREATE TABLE tSchoolClass (
schoolClassId int IDENTITY(1,1) NOT NULL,
CONSTRAINT PK_tSchoolClass PRIMARY KEY (schoolClassnId));
CREATE TABLE tStudents (
studentId int IDENTITY(1,1) NOT NULL,
schoolClassint NOT NULL,
CONSTRAINT PK_tStudents PRIMARY KEY (studentId),
CONSTRAINT FK_tStudent_tSchoolClass FOREIGN KEY (schoolClassId) REFERENCES tSchoolCLass (SchoolClassId));
The exception NULL not allowed for column "schoolClassId" is clearly saying schoolClassId cannot be null.
Its the nullable = false property, that would enforce the not null constraint on the column schoolClassId which can be translated to schoolClassId bigint NOT NULL in the student create table.
The insertable=true on schoolClassId column would mean the column is included in the insert query. So whenever an instance of SchoolClass is persisted, the associated Student instances will be persisted too. The student entity insert will include the SchoolClassId column , its value referencing to SchoolClass id's instance, which is not null in this case.
So in short, anytime the column schoolClassId is null, the constraint violation will be thrown, so keeping insertable=false, you would need to set nullable = true if you have to get rid of the violation.

JPA cascading merge when it shouldn't

I'm having a hard time understanding this JPA behavior which to me doesn't seem to follow the specification.
I have 2 basic entities:
public class User {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#OrderBy("sequence ASC")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.REMOVE })
private final Set<UserProfile> userprofiles = new HashSet<UserProfile>(0);
//Ommiting rest of fields since they aren't relevant
}
public class UserProfile {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "userID", nullable = false, foreignKey = #ForeignKey(name = "FK_UserProfile_User"))
private User user;
//Ommiting rest of fields since they aren't relevant
}
As you can see I only have cascading set to REMOVE, the behavior will be the same if I don't have cascade set at all.
Now if I call:
User user = new User();
user.setId(UUIDGenerator.generateId());
UserProfile userProfile = new UserProfile();
userProfile.setId(UUIDGenerator.generateId());
userProfile.setUser(user);
user.getUserProfiles().add(userProfile);
em.merge(user);
merge will throw an exception.
I see Hibernate is executing a SQL query against the UserProfile table:
select userprofil0_.userProfileID as userProf1_4_0_, userprofil0_.profileID as profileI3_4_0_, userprofil0_.sequence as sequence2_4_0_, userprofil0_.userID as userID4_4_0_ from UserProfile userprofil0_ where userprofil0_.userProfileID=?
And then it will throw an exception
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.mytest.domain.UserProfile with id 6aaab891-872d-41e6-8362-314601324847;
Why is this query even called?
Since I don't have cascade type set to MERGE in userprofiles my expectation would be that JPA/Hibernate would simply ignore the entities inside userprofiles set and only insert/update the user record, doesn't this go against the JPA specs?
If I change cascadetype to MERGE things will work as expected and both User and UserProfile will be added to the database, so no problem there. What puzzles me is why is Hibernate querying the database and erroring out about an entity that's not supposed to be merged at all since I don't have it set to cascade.
This is more of an academic scenario that I ran into, of course I could simply clear the userprofiles set and things would work, but I'm trying to understand why the above behavior happens since I'm probably missing some crucial piece of information about how merge works. It seems it will always try to attach all entities to the session regardless cascade type being set or not.
Why is this query even called?
It's because you are trying to merge the entity, in JPA merge() is used to make the entity managed/attached. To "merge" User, JPA needs to still maintian the references it holds(UserProfile). In your case its not trying to persist UserProfile its trying to get a reference to it to merge User. Read here
If you use persist rather than merge this should not happen.

Categories