OneToOne mapping issue for non primary field - jpa

I am trying to map a non primary key column between 2 tables using one-to-one mapping through JPA.
The OneToOne is not performing the join on the mentioned column rather it is picking up the Id field.
Below is the table structure:
Person table
id (PK)
name
college
College table
id (PK)
clg_name
location
Location table
id (PK)
loc_name
I need to provide OneToOne mapping between College and Location using the columns location and loc_name respectively. I have tried using the #NaturalId, #MapsId and by providing the reference column name. Still it uses the id field
//person
#Entity
#Table(name = "PERSON", schema = "DETAILS")
#SecondaryTables({
#SecondaryTable(name = "COLLEGE", schema = "DETAILS")
})
class Person{
Person(){
this.college = new College();
}
#Id
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "COLLEGE_NAME", table = "COLLEGE", nullable = false)
private String college;
#OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "person")
#JoinColumn(name = "ID")
private College college;
//getter setters
}
//college
#Entity
#Table(name = "COLLEGE", schema = "DETAILS")
class College{
College(){
}
#Id
#MapsId
#OneToOne()
#JoinColumn(name = "ID")
private Person person;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER, mappedBy = "college")
#JoinColumn(referencedColumnName = "LOC_NAME")
private Location location;
#Column(name = "LOCATION", nullable = false)
private String loc;
//getter setters
}
//location
#Entity
#Table(name = "LOCATION", schema = "DETAILS")
class Location{
Location(){}
#Id
#Column(name = "ID")
private Long collegeId;
#MapsId
#OneToOne()
#JoinColumn(name = "LOC_NAME", referencedColumnName ="LOCATION", nullable = false, unique = true)
private College college;
#Column(name = "LOC_NAME", nullable = false)
private String locName;
//getter setters
}
In the above code, I am facing in OneToOne mapping issue using the location name columns. I am querying the Person object from the JPA repository by querying "from Person p where p.id = :id".
The generated JPA queries in logs for 1to1 mapping appears to be
select from details.college college0_ left outer join details.location location1_ on college0_.id=location1_.locName where college0_.id=?
Error:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [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
Caused by: java.sql.SQLSyntaxErrorException: ORA-01722: invalid number
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
If I remove #MapsId from Location then I get below error:
org.hibernate.AnnotationException: A Foreign key refering has the wrong number of column. should be 0

Related

How to update records that are dependent on a different table using JPA Criteria API?

I have the following entities with the one-to-one relationship:
#NoArgsConstructor
#Data
#DynamicUpdate
#Table(name = "product")
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.UUID)
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#Column(name = "feed", length = 100, nullable = false)
private String feed;
// Omitted columns
#ToString.Exclude
#OneToOne(mappedBy = "product", cascade = CascadeType.ALL)
private PushPermission pushPermission;
}
#Data
#NoArgsConstructor
#Table(name = "push_permission")
#Entity
public class PushPermission implements Serializable {
#Id
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
// Omitted columns
#ToString.Exclude
#OneToOne
#JoinColumn(name = "id")
#MapsId
private Product product;
}
I would like to update all records in PushPermission where feed (column from Product) is not equal to PROMO using JPA Criteria API.
I have used the following CriteriaUpdate:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<PushPermission> criteriaUpdate = cb.createCriteriaUpdate(PushPermission.class);
Root<PushPermission> root = criteriaUpdate.from(PushPermission.class);
criteriaUpdate.set("exampleField", true);
Predicate selectedProductsPredicate = root.get("id").in(ids);
Predicate skipFeedPredicate = cb.notEqual(root.get("product").get("feed"), "PROMO");
criteriaUpdate.where(cb.and(selectedProductsPredicate, skipFeedPredicate));
Query query = entityManager.createQuery(criteriaUpdate);
query.executeUpdate();
but I got the following error message:
ERROR: missing FROM-clause entry for table "p2_0"
Generated update statement by Hibernate:
update
push_permission
set
exampleField=?,
where
id in(?,?)
and p2_0.feed!=?
Besides I tried to use joining:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<PushPermission> criteriaUpdate = cb.createCriteriaUpdate(PushPermission.class);
Root<PushPermission> root = criteriaUpdate.from(PushPermission.class);
Join<PushPermission, Product> productJoin = root.join("product");
criteriaUpdate.set("exampleField", true);
Predicate selectedProductsPredicate = root.get("id").in(ids);
Predicate skipFeedPredicate = cb.notEqual(productJoin.get("feed"), "PROMO");
criteriaUpdate.where(cb.and(selectedProductsPredicate, skipFeedPredicate));
Query query = entityManager.createQuery(criteriaUpdate);
query.executeUpdate();
but I got the following message:
The root node [me.foo.app.PushPermission] does not allow join/fetch
Hibernate didn't generate any update statement.
I use Postgres SQL 14.5 and I know I can do the native query which works:
update push_permission set exampleField=true from product where push_permission.id=product.id and product.feed<>'PROMO';
but I wonder I can do it with the use of JPA Criteria API.
I use Spring Boot 3.0.2 that implies Hibernate 6.
That's not yet possible, but support for that is on the roadmap. For now, you'd have to use an exists subquery to model this i.e.
update PushPermission p
set p.exampleField=true
where exists (
select 1
from product pr
where p.id=pr.id
and pr.feed<>'PROMO';
)

Handling reduntant columns with hibernate/jpa/spring data

i'm kinda struggling mapping the following schema with hibernate
table_a (A1_ID,A2_ID) --> PK = (A1_ID, A2_ID)
table_b (A1_ID, A2_ID, B1_ID) --> PK =(A1_ID, A2_ID, B1_ID)
where table_b's A1_ID and A2_ID should be foreingkey referencing respective table_A's columns
There is a one-to-many from TABLE_A to TABLE_B where TABLE_B's primary key is partially shared with TABLE_A's primary key
What I've tried so far
#Data
#Entity
#Table(name = "table_a")
#IdClass(TableA.TableAKey.class)
public class TableA {
#Id
#Column(name = "A1_ID)
private String a1_id;
#Id
#Column(name = "A2_ID)
private String a2_id;
#OneToMany(mappedBy = "tableA",fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<TableB> tableB;
#Data
static class TableAKey implements Serializable {
private String a1_id;
private String a2_id
}
}
**CHILD ENTITY**
#Data
#Entity
#Table(name = "table_b")
#IdClass(TableB.TableBKey.class)
public class TableB {
#Id
#Column(name = "B1_ID)
private String b1_id;
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "a1_id", insertable = false, updatable = false),
#JoinColumn(name = "a2_id", insertable = false, updatable = false)
)}
private TableA tableA;
#Column(name = "A1_ID)
private String a1_id;
#Column(name = "A2_ID)
private String a2_id;
#Data
static class TableAKey implements Serializable {
private String b1_id;
private TableA tableA;
}
}
I was expecting i could be able to do something like this:
TableA tableA = new TableA();
t.setA1_id("a1id");
t.setA2_id("a2id");
TableB tableB = new TableB();
tableB.setB1Id("b1Id");
tableA.setTableB(Arrays.asList(tableB));
tableARepository.save(tableA);
And the code above I was expecting to "magically" perform the following insert at DB
INSERT INTO table_A (A1_ID,A2_ID) VALUES ('a1id',a2id');
INSERT INTO table_B (A1_ID,A2_ID, B1_ID) VALUES ('a1id',a2id','b1id')
but instead i get a "the column index is out of range: n, number of columns n-1".
I also tried with some embeddedId approach, using referenceColumnName but nothing.
Am I doing something wrong in the mapping or in the object creation process?
The problem is a lot similar to the following
https://hibernate.atlassian.net/browse/HHH-14340

Spring Data Envers Entity must not be null

Suppose we have audited entities with #OneToOne relation:
#Entity
#Audited
#Table(name = "product")
public class Product {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "active")
private boolean active;
#OneToOne(mappedBy="product", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private ProductPrice productPrice;
}
#Audited
#Entity
#Table(name = "product_price")
public class ProductPrice {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "amount")
private Long amount;
#OneToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "product_id", nullable = false)
private Product product;
}
And RevisionService with the method to get revisions and find changes:
#Transactional
public Page<Revision<Long, Product>> getGroupRevisions(Long productId, int page) {
Page<Revision<Long, Product>> revisions = productRepository.findRevisions(productId, PageRequest.of(page, 5, RevisionSort.desc()));
Long priceId = revisions.getContent().get(0).getEntity().getProductPrice().getId();
Page<Revision<Long, ProductPrice>> priceRevisions = productPriceRepository.findRevisions(priceId, PageRequest.of(page, 5, RevisionSort.desc()));
return revisions;
}
Now, If I create new Product and ProductPrice records and then make changes into Product more then 5 times (5 RevInfo records would generated), I get exception:
java.lang.IllegalArgumentException: Entity must not be null!
at org.springframework.util.Assert.notNull(Assert.java:198)
at org.springframework.data.history.AnnotationRevisionMetadata.<init>(AnnotationRevisionMetadata.java:55)
at org.springframework.data.envers.repository.support.EnversRevisionRepositoryImpl.getRevisionMetadata(EnversRevisionRepositoryImpl.java:237)
at org.springframework.data.envers.repository.support.EnversRevisionRepositoryImpl.lambda$toRevisions$1(EnversRevisionRepositoryImpl.java:223)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1837)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at org.springframework.data.envers.repository.support.EnversRevisionRepositoryImpl.toRevisions(EnversRevisionRepositoryImpl.java:226)
at org.springframework.data.envers.repository.support.EnversRevisionRepositoryImpl.getEntitiesForRevisions(EnversRevisionRepositoryImpl.java:196)
at org.springframework.data.envers.repository.support.EnversRevisionRepositoryImpl.findRevisions(EnversRevisionRepositoryImpl.java:163)
After debugging I saw that this "null" entity was proxied by Hibernate and Spring Data envers could not resolve revision number in this point:
Number revNo = this.enversService.getRevisionInfoNumberReader().getRevisionNumber(revision);
Here is the link to github test project: https://github.com/aquariusmaster/spring-data-envers-bug
So my question is this a bug in Spring Data Envers or I miss something in the configuration?
As spring-data-envers team replied, upgrading boot version to 2.3.1.RELEASE solve the problem:
https://github.com/spring-projects/spring-data-envers/issues/34#issuecomment-651681687

JPA expecting ID column on joined table

I'm attempting to represent a join table with JPA, however the generated SQL is expecting an id field on one of the tables.
The generated SQL is very close, but appears an ID column is expected on Permission table and used as the FK on the join table, which I was hoping not to do. See schema below:
You can see I am not using an ID column on permission table, instead letting the text representation of the permission be the key.
User.java
#Entity
#Data
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int accountId;
private String name;
private String email;
private String password;
private boolean admin;
#ManyToMany
#JoinTable(
name = "user_permission",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "permission"))
private List<Permission> permissions;
}
Permission.java
#Data
#Entity
public class Permission {
#Id
private String permission;
private String permissionName;
private String permissionDescription;
}
Generated SQL
select
permission0_.id as id1_2_0_,
permission0_.permission as permissi2_2_0_,
permission1_.permission as permissi1_0_1_,
permission1_.permission_description as permissi2_0_1_,
permission1_.permission_name as permissi3_0_1_
from user_permission permission0_
inner join permission permission1_ on permission0_.permission=permission1_.permission
where permission0_.id=?
Error
ERROR 15056 --- [tp1962586186-25] o.h.engine.jdbc.spi.SqlExceptionHelper : Unknown column 'permission0_.id' in 'field list'
The problem was join columns name. It should have been mapped to the mapping tables FK instead of the parent tables PK. Above example is fixed with this change
#ManyToMany
#JoinTable(
name = "user_permission",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "permission"))
private List<Permission> permissions;

How to name Foreign Key with #CollectionTable in JPA?

I need help with resolving our problem with naming FK in JPA. We have one embeddable entity e.g. Foo which is used as collection in another one entity Bar.
embeddable entity:
#Embeddable
public class Foo{
#Column(name = "foo1")
private String foo1;
#Column(name = "foo2")
private String foo2;
}
main entity:
#Entity
#Table(name = "Bar")
public class Bar {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
#Id
private Long id;
#ElementCollection
#CollectionTable(name = "foos", joinColumns = #JoinColumn(name = "bar_id", foreignKey = #ForeignKey(name = "foo_bar_fk"), nullable = false))
private List<Bar> bars;
}
When I generated tables in database (postgres) foreign key is called fdk44l345k64 instead foo_bar_fk. Can you tell me how to fix it? Thanks.