Create a table by combining one column from another table in Java - java

So after some research, I could not find something relevant.
I use one table as below:
#Table(name="product")
public class Product {
#Id
#GeneratedValue
private long productId;
// other irrelevant columns and code goes here
}
Now, I want to create another table that it goes like:
To make that, I tried something like this by following other examples or samples:
#Table(name="combined_products")
public class CombinedProducts {
#EmbeddedId
protected CombinedProductsPK bridgeId;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "product_1", referencedColumnName = "product_id"),
#JoinColumn(name = "product_2", referencedColumnName = "product_id")
})
#Column(name = "notes")
private String notes;
public ProductMatrix() {
bridgeId = new CombinedProductsPK();
}
// irrelevant code again
}
and the CombinedProductsPK:
#Embeddable
public class CombinedProductsPK implements Serializable {
public Long product_1;
public Long product_2;
public CombinedProductsPK() {}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
CombinedProductsPK b = (CombinedProductsPK)obj;
if (b == null) {
return false;
}
return b.product_1.equals(product_1) && b.product_2.equals(product_2);
}
#Override
public int hashCode() {
return (int)(product_1 + product_2);
}
}
and all seems to work perfect.
BUT, my problem is, when I take a look at the database and specific to the combined_products table, there is no FOREIGN_KEY constraint. Is there any way to describe this constraint in Java, or I must manually, in the Java part, take care of this??
This is how my table looks like in the MySQLWorkbench
CREATE TABLE `combined_products` (
`product_1` bigint(20) NOT NULL,
`product_2` bigint(20) NOT NULL,
`notes` varchar(255) DEFAULT NULL,
PRIMARY KEY (`product_1`,`product_2`)
)
I'm in a dead end here, so maybe I follow a wrong route. Every recommendation is accepted! Thanks in advance...

i didn't got what you mean. but maybe you need to try #JoinTable for that ?
enter link description here
i hope it helps.

Related

Using cascade.all in a many-to-many with extra columns association

After searching for quite a long time I'm wondering if my code is wrong or if it's simply impossible in Hibernate.
I'll use a fake example to explain my problem. Let's say I have three tables in my database, post, posttag and tag. A post can have multiple tags and a tag can be used my multiple posts, so it's a many-to-many association but there's also extra columns in the table between (posttag).
example entity relationship diagram
So, in my code, I have 3 entity and one more class for the composite key of posttag.
PostEntity:
#Entity
#Table(name = "post", catalog = "fakeExample")
public class PostEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDPOST", nullable = false)
private Integer idpost;
#OneToMany(mappedBy = "post", targetEntity = PostTagEntity.class, cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTagEntity> listOfPostTag = new ArrayList<>();
public void setIdpost(Integer idpost)
{
this.idpost = idpost;
}
public Integer getIdpost()
{
return this.idpost;
}
public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
if (listOfPostTag != null)
{
this.listOfPostTag.clear();
this.listOfPostTag.addAll(listOfPostTag);
}
}
public List<PostTagEntity> getListOfPostTag()
{
return this.listOfPostTag;
}
}
TagEntity:
#Entity
#Table(name = "tag", catalog = "fakeExample")
public class TagEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDTAG", nullable = false)
private Integer idtag;
#OneToMany(mappedBy = "tag", targetEntity = PostTagEntity.class, cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTagEntity> listOfPostTag = new ArrayList<>();
public void setIdtag(Integer idtag)
{
this.idtag = idtag;
}
public Integer getIdtag()
{
return this.idtag;
}
public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
if (listOfPostTag != null)
{
this.listOfPostTag.clear();
this.listOfPostTag.addAll(listOfPostTag);
}
}
public List<PostTagEntity> getListOfPostTag()
{
return this.listOfPostTag;
}
}
PostTagEntity:
#Entity
#Table(name = "posttag", catalog = "fakeExample")
public class PostTagEntity implements Serializable
{
#EmbeddedId
private PostTagEntityKey compositePrimaryKey = new PostTagEntityKey();
#Column(name = "EXTRACOLUMN")
private Integer extraColumn;
#ManyToOne
#JoinColumn(name = "IDPOST", referencedColumnName = "IDPOST", nullable = false)
#MapsId("idpost")
private PostEntity post;
#ManyToOne
#JoinColumn(name = "IDTAG", referencedColumnName = "IDTAG", nullable = false)
#MapsId("idtag")
private TagEntity tag;
public void setIdpost(Integer idpost)
{
this.compositePrimaryKey.setIdpost(idpost);
}
public Integer getIdpost()
{
return this.compositePrimaryKey.getIdpost();
}
public void setIdtag(Integer idtag)
{
this.compositePrimaryKey.setIdtag(idtag);
}
public Integer getIdtag()
{
return this.compositePrimaryKey.getIdtag();
}
public void setExtraColumn(Integer extraColumn)
{
this.extraColumn = extraColumn;
}
public Integer getExtraColumn()
{
return this.extraColumn;
}
public void setPost(PostEntity post)
{
this.post = post;
}
public PostEntity getPost()
{
return this.post;
}
public void setTag(TagEntity tag)
{
this.tag= tag;
}
public TagEntity getTag()
{
return this.tag;
}
}
PostTagEntityKey:
#Embeddable
public class PostTagEntityKey implements Serializable
{
#Column(name = "IDPOST", nullable = false)
private Integer idpost;
#Column(name = "IDTAG", nullable = false)
private Integer idtag;
public PostTagEntityKey()
{
}
public PostTagEntityKey(Integer idpost, Integer idtag)
{
this.idsalle = idsalle;
this.idequipement = idequipement;
}
public void setIdpost(Integer value)
{
this.idpost = value;
}
public Integer getIdpost()
{
return this.idpost;
}
public void setIdtag(Integer value)
{
this.idtag = value;
}
public Integer getIdtag()
{
return this.idtag;
}
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (this.getClass() != obj.getClass())
{
return false;
}
PostTagEntityKey other = (PostTagEntityKey) obj;
if (this.idpost == null ? other.idpost != null : !this.idpost.equals((Object) other.idpost))
{
return false;
}
if (this.idpost == null ? other.idpost != null
: !this.idtag.equals((Object) other.idtag))
{
return false;
}
return true;
}
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((idpost == null) ? 0 : idpost.hashCode());
result = prime * result + ((idtag == null) ? 0 : idtag.hashCode());
return result;
}
}
Also, I am using Spring, so here are the few class involved when I do an insert or something else. I don't think the problem come from here but just in case.
PostService:
public interface PostService
{
public List<PostEntity> findAll();
public Optional<PostEntity> findById(int var1);
public PostEntity save(PostEntity var1);
public void deleteById(int var1);
}
PostImpl:
#Service
public class PostImpl implements PostService
{
#Autowired
private PostRepository repository;
#Override
public List<PostEntity> findAll()
{
return this.repository.findAll();
}
#Override
public Optional<PostEntity> findById(int id)
{
return this.repository.findById(id);
}
#Override
public PostEntity save(PostEntity toSave)
{
return (PostEntity) this.repository.save(toSave);
}
#Override
public void deleteById(int id)
{
this.repository.deleteById(id);
}
}
PostRepository:
#Repository
public interface PostRepository extends JpaRepository<PostEntity, Integer>, JpaSpecificationExecutor<PostEntity>, PagingAndSortingRepository<PostEntity, Integer>
{
}
So when I need to insert a post, I just use something like this:
#Autowired
PostService postService;
public PostEntity createPost(PostEntity post)
{
return this.postService.save(post);
}
For me, the expected behaviour of Hibernate would be:
when I insert a post, to insert the post and insert every postTag in listOfPostTag
when I update a post, to remove every missing postTag in listOfPostTag, to add every new postTag in listOfPostTag, to update the change postTag in listOfPostTag and to update the post
when I delete a post, to delete every postTag in listOfPostTag and to delete the post
However, when I try to insert a post, I have an error. And from the many tests I've done, it seems that Hibernate insert the post successfully and then tries to insert the postTags, but fails because the idPost in PostTagEntityKey is still null. I would have expected that Hibernate updated it with the id from the inserted post.
So my question is can hibernate do that in the case I described? Or do I have to do it by hand (by not using the cascade mode for insert/update)?
The explanation might be that it's impossible with composite keys, bidirectional, something else or that it's just not something hibernate is supposed to do. I'd like to know if it's possible and if it is, what did I do wrong?
If it's not possible, I wonder what is the point of inserting things in cascade if you can't even do it for a thing as common as an intermediary table.
I haven't tried to code this fake example but I believe it would have the same result as I changed almost nothing from the original. Also I skipped the part where I create the postEntity because in my case it's parsed from JSON. I used the debugger and tried different things, so I'm almost sure the problem doesn't come from here. Every field is filled, even the idTag in PostTagEntityKey. It's just the idPost in PostTagEntityKey that is null because the post hasn't been inserted yet. And hibernate doesn't update it after inserting the post. I start to believe there's no way for the cascade mode to update it and maybe it's the case.
EDIT :
So, thanks to the comment of #a.ghavidel, I realised I've never tried to set the post in posttag.
So I've changed the function setListOfPostTag in post entity like this :
public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
if (listOfPostTag != null)
{
this.listOfPostTag.clear();
for(PostTagEntity postTag : listOfPostTag)
{
postTag.setPost(this);
this.listOfPostTag.add(postTag);
}
}
}
and the problem has changed as well. Before that modification, it was telling me that it couldn't insert postTag because idPost was null. And now it's telling me "org.hibernate.PersistentObjectException: detached entity passed to persist: com.fakeexample.TagEntity".
So I think, to retreive the primary key, the entity postTag needed the variable Post to be set. The List listOfPostTag in Post wasn't enough. So my original problem is now fixed I think.
My new problem is that hibernate seems to consider that the tag in postTag is "detached" and I'm not sure what that mean.
In my case, the tag in postTag doesn't come from a function of hibernate, it has been parsed from json so maybe that's why. However I don't need it to be persisted, in theory I just need its id to insert the postTag and there is no cascade in postTag.
I've tried replacing CascadeType.All with CascadeType.Merge cause some people said it worked for them but when I do this it just doesn't insert any postTag when I insert a post. However it seems to work very well when I update a post.
I think I'm very close to the solution. I'm going to try a few things and I will edit this post if I find the answer.
EDIT 2 :
So, I've tried a few things and made some progress. The object is detached because it hasn't been created by hibernate. There is no problems during a merge but it can't be persisted.
A solution might be to do everything by hand in the create function...
But the proper solution would be to use the function getReference() from entityManager. It doesn't generate any unwanted select or update, it just create a proxy object and only require the id in parameter.
However the entityManager is not accessible in spring I think, but we can use the function getOne() from a repository which is mapped to the getReference() method. Basically, if I understood correctly, the function getOne() is supposed to be using lazy loading, so the object isn't loaded as long as we don't need it to be loaded.
I tried to use this function and indeed my code is now working correctly.
But the problem is : the function getOne() is deprecated.
The function has been replaced by getById() but I'm really not sure it use lazy loading too because I'm already using this function a lot and not to create proxy object at all. Also, I know the attribute "fetch = FetchType.LAZY" cant be put in #OneToMany, so, if I didn't put it in my code I suppose I'm not using lazy loading and it will do a lot of unwanted select. Also I don't think I should be using lazy loading all the time neither, I heard it can be troublesome by sometime generating one select for each entity of a collection instead of a single select with lazy loading...
So I still need to make some research to know how to do it with non derprecated function.
EDIT 3 :
Okay so no, the function I was using was findById and not getById. So getById is probably the solution to my problem. I'm gona check the doc to confirm and test it out.
FINAL EDIT :
Well it's working for the insert but I have now a problem during the update. Basically it tells me the posttag already exists in the DB. Probably because I replace the listOfPostTag from the findById with a list I created myself by parsing it. The solution is probably to edit the objects in this list. Now that I understand how the things work in hibernate I need to review the entirety of my code to apply these principles.
In the end, it seems that hibernate is not very friendly restful apis and we have to process every object so hibernate can recognize them.
To summerize, my problems were :
first : consistency. I didn't set the post in postTag object.
Second : attach and detach objects. I didn't used getById function to create an Object recognized by hibernate for the Taf Object.
Third : updating. When I updated a post, I didn't update the objects in listOfPostTag, I just replaced it with another list, with only objects not recognized by hibernate. I should have updated the list Object by Object I guess.
You should use this method in PostEntity when you are creating one
private void addPostTag(PostTagEntity postTag){
postTag.setPost(this);
this.listOfPostTag.add(postTag);
}
I think this will fix your problem

I'm trying to understand LazyInitializationException and #Transactional

This is a follow-up question to my previous one How to model packages, versions and licenses?.
Here is my database setup.
V1__create_table_license.sql
CREATE TABLE IF NOT EXISTS license (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
reference TEXT NOT NULL,
is_deprecated_license_id BOOLEAN NOT NULL,
reference_number INTEGER NOT NULL,
license_id TEXT NOT NULL,
is_osi_approved BOOLEAN NOT NULL
);
INSERT INTO license
("name",reference,is_deprecated_license_id,reference_number,license_id,is_osi_approved)
VALUES
('MIT License','./MIT.json',false,275,'MIT',true);
V2__create_npm_package.sql
CREATE TABLE IF NOT EXISTS npm_package (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL
);
INSERT INTO npm_package
(name, description)
VALUES
('react', 'React is a JavaScript library for building user interfaces.'),
('react-router-dom', 'DOM bindings for React Router'),
('typescript', 'TypeScript is a language for application scale JavaScript development'),
('react-dom', 'React package for working with the DOM.');
V3__create_npm_version.sql
CREATE TABLE IF NOT EXISTS npm_package_version (
npm_package_id BIGINT NOT NULL REFERENCES npm_package,
version TEXT NOT NULL,
license_id INTEGER NOT NULL REFERENCES license,
UNIQUE(npm_package_id, version)
)
Here are my Java objects.
License.java
#Entity
public class License {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String reference;
private Boolean isDeprecatedLicenseId;
private Integer referenceNumber;
private String name;
private String licenseId;
private Boolean isOsiApproved;
}
LicenseRepository.java
public interface LicenseRepository extends JpaRepository<License, Integer> {
License findByLicenseIdIgnoreCase(String licenseId);
}
NpmPackage.java
#Entity
public class NpmPackage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#OneToMany(mappedBy = "npmPackage", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NpmPackageVersion> versions = new ArrayList<>();
public NpmPackage() {}
public void addVersion(NpmPackageVersion version) {
this.versions.add(version);
version.setNpmPackage(this);
}
public void removeVersion(NpmPackageVersion version) {
this.versions.remove(version);
version.setNpmPackage(null);
}
}
#Entity
public class NpmPackageVersion {
public NpmPackageVersion() {}
public NpmPackageVersion(String version, License license) {
this.setVersion(version);
this.license = license;
}
#EmbeddedId private NpmPackageIdVersion npmPackageIdVersion = new NpmPackageIdVersion();
#MapsId("npmPackageId")
#ManyToOne(fetch = FetchType.LAZY)
private NpmPackage npmPackage;
#ManyToOne(fetch = FetchType.LAZY)
private License license;
#Embeddable
public static class NpmPackageIdVersion implements Serializable {
private static final long serialVersionUID = 3357194191099820556L;
private Long npmPackageId;
private String version;
// ...
}
public String getVersion() {
return this.npmPackageIdVersion.version;
}
public void setVersion(String version) {
this.npmPackageIdVersion.version = version;
}
}
MyRunner.java
#Component
class MyRunner implements CommandLineRunner {
#Autowired LicenseRepository licenseRepository;
#Autowired NpmPackageRepository npmPackageRepository;
#Override
// #Transactional
public void run(String... args) throws Exception {
// get license from database
var license = licenseRepository.findByLicenseIdIgnoreCase("mit");
// get package from db
var dbPackage = npmPackageRepository.findByNameIgnoreCase("react");
var version = new NpmPackageVersion("1.0.0", license);
dbPackage.addVersion(version);
npmPackageRepository.save(dbPackage);
}
}
In my previous question I got the answer to use fetch = FetchType.EAGER but then I learned that this is not ideal. I'd like to use lazy fetching.
#OneToMany(mappedBy = "npmPackage", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<NpmPackageVersion> versions = new ArrayList<>();
So I removed the eager fetching and run into an error.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.bom.NpmPackage.NpmPackage.versions, could not initialize proxy - no Session
With the #Transactional annotation everything works. Why is this the case? I tried to read everything online but I still don't really get it. I understand that the database session is closed at some point and I wonder where exactly this is the case. I also wonder if I could do something about, e.g. I tried to fetch all versions to ensure they are loaded before I add another one.
So do I really have to use #Transactional or is there another solution? I just want to understand the "magic" going on :)
Thank you very much!
When you use FetchType.LAZY, Hibernate ORM doesn't really return an initialized collection when you find the entity. The association is going to be a proxy and when you need access to the collection, Hibernate ORM is going to query the database and get it.
To achieve this, the entity (the NpmPackage) needs to be in a managed state. If the entity is not managed and you try to access a lazy association (versions in this case), you get the LazyInitializationException.
In your example, when you use #Transactional, the entity stays managed for the duration of the method. Without it, it becomes not managed as soon as you return from findByNameIgnoreCase.
If you know that you will need the association versions, you could also use a fetch join query to get the NpmPackage:
from NpmPackage p left join fetch p.versions where p.name=:name
This way the associations stays lazy but you can get it with a single query.

Hibernate sometimes doesn't extract uuid referenced entity

I have two entities, let's name them University and Student, Student is unidirectional ManyToOne with University they both extends base class BasicUUIDEntity:
#MappedSuperclass
public class BasicUUIDEntity implements Serializable {
protected UUID id;
#Id
#Column(name = "id", columnDefinition = "binary(16)")
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BasicUUIDEntity that = (BasicUUIDEntity) o;
return id == null || id.equals(that.id);
}
#Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
The structure of Student and University doesn't really matters, the important things is:
Student.class:
#Entity
#Table(name = "students")
public static class Student extends BasicUUIDEntity {
private University univ;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "univ_id", referencedColumnName = "id")
public University getUniversity() {
return univ;
}
public void setUniversity(University univ) {
this.univ = univ;
}
}
University.class:
#Entity
#Table(name = "universities")
public static class University extends BasicUUIDEntity {
//of course it does contain some fields, but they are strings, etc.
}
The tables are created as follows:
CREATE TABLE students
(
id BINARY(16) NOT NULL,
univ_id BINARY(16),
PRIMARY KEY (id),
CONSTRAINT FK_STUDENT_UNIV
FOREIGN KEY (univ_id) REFERENCES memory_type (id)
ON DELETE SET NULL
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8;
CREATE TABLE universities
(
id BINARY(16) NOT NULL,
PRIMARY KEY (id)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8;
The Issue is sometimes, on really rare occasions Hibernate doesn't extract that University entity along with Student, meaning that when I do student.getUniversity() it returns null, and in debugger it is also null. BUT the students table contains exactly the same univ_id as expected University of this student, and so does university contains that id. I am completely sure that hibernate session is not closed, and I've tried to execute exactly the same query as hibernate does, and it does return expected id of the university, joins them, etc. The thing is that most of the time It does work well, and such issue happens very rarely due to unknown reasons, what is more strange is that the table does contains that id, so it seems like it is not an MySQL issue.
Also worth mention, I am using Spring Data JPA.
Have anyone encountered such behavior? Could it be due to Inheritance/Data JPA/Java 9/Anything other?
PS.
Please ignore any typo in the code, it is just an example
Versions:
Hibernate: 5.2.12.Final,
Spring Data JPA: 2.0.5.RELEASE,
HikariCP: 2.7.8,
MySQL connector: 6.0.6,
Java: 9
MySQL: Ver 14.14 Distrib 5.7.21
Solved, finally
tl;dr
Put fetch = FetchType.LAZY to ManyToOne relation, so in my example it becomes:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "univ_id", referencedColumnName = "id")
public University getUniversity() {
return univ;
}
Description
I am not still sure why it behaves like this, but it seems that Hibernate does not extract many-to-one relations at all, if they're eager. It have some sense as this way there will be circular dependencies, but I thought that PersistenceSet will deal with such issues. What is even more strange - just the same structure actually works without eager extraction if I use Long instead of UUID.

JPA - Check existence of value in another table as Boolean value

I have a (abbreviated) class that looks like this:
#Entity
#Table
#SecondaryTable(
name = "SUPER_ADMIN",
pkJoinColumns = #PrimaryKeyJoinColumn(
name = "PERSON_ID",
referencedColumnName = "PERSON_ID"))
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
// getters/setters omitted for brevity
}
The SUPER_ADMIN table has only one column: PERSON_ID. What I would like to do is add private Boolean superAdmin to Person where it would be true if the PERSON_ID is present in that table.
Is this even possible? I am using Hibernate as my JPA provider, so I'm open to proprietary solutions as well.
UPDATE
It seems like I should have done more homework. After poking around, I see that #SecondaryTable does inner joins and not outer joins. Therefore, my idea here will not work at all. Thanks to #Elbek for the answer -- it led me to this revelation.
You can use JPA callback methods.
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
#Transient
private transient Boolean superAdmin = false;
// This method will be called automatically when object is loaded
#PostLoad
void onPostLoad() {
// BTW, personId has to be present in the table since it is id column. Do you want to check if it is 1?
superAdmin = personId == 1;
}
}
or you can create easy getter method.
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
boolean isSuperAdmin() {
return personId == 1;
}
}
You can't have an optional relationship with a #SecondaryTable. You do not have any other choice than using a #OneToOne optional relationship in that case.

Does Hibernate support one-to-one associations as pkeys?

Can anyone tell me whether Hibernate supports associations as the pkey of an entity? I thought that this would be supported but I am having a lot of trouble getting any kind of mapping that represents this to work. In particular, with the straight mapping below:
#Entity
public class EntityBar
{
#Id
#OneToOne(optional = false, mappedBy = "bar")
EntityFoo foo
// other stuff
}
I get an org.hibernate.MappingException: "Could not determine type for: EntityFoo, at table: ENTITY_BAR, for columns: [org.hibernate.mapping.Column(foo)]"
Diving into the code it seems the ID is always considered a Value type; i.e. "anything that is persisted by value, instead of by reference. It is essentially a Hibernate Type, together with zero or more columns." I could make my EntityFoo a value type by declaring it serializable, but I wouldn't expect this would lead to the right outcome either.
I would have thought that Hibernate would consider the type of the column to be integer (or whatever the actual type of the parent's ID is), just like it would with a normal one-to-one link, but this doesn't appear to kick in when I also declare it an ID. Am I going beyond what is possible by trying to combine #OneToOne with #Id? And if so, how could one model this relationship sensibly?
If the goal is to have a shared primary key, what about this (inspired by the sample of Java Persistence With Hibernate and tested on a pet database):
#Entity
public class User {
#Id
#GeneratedValue
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address shippingAddress;
//...
}
This is the "parent" class that get inserted first and gets a generated id. The Address looks like this:
#Entity
public class Address implements Serializable {
#Id #GeneratedValue(generator = "myForeignGenerator")
#org.hibernate.annotations.GenericGenerator(
name = "myForeignGenerator",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "user")
)
#Column(name = "ADDRESS_ID")
private Long id;
#OneToOne(mappedBy="shippingAddress")
#PrimaryKeyJoinColumn
User user;
//...
}
With the above entities, the following seems to behave as expected:
User newUser = new User();
Address shippingAddress = new Address();
newUser.setShippingAddress(shippingAddress);
shippingAddress.setUser(newUser); // Bidirectional
session.save(newUser);
When an Address is saved, the primary key value that gets inserted is the same as the primary key value of the User instance referenced by the user property.
Loading a User or an Address also just works.
Let me know if I missed something.
PS: To strictly answer the question, according to Primary Keys through OneToOne Relationships:
JPA 1.0 does not allow #Id on a OneToOne or ManyToOne, but JPA 2.0 does.
But, the JPA 1.0 compliant version of Hibernate
allows the #Id annotation to be used on a OneToOne or ManyToOne mapping*.
I couldn't get this to work with Hibernate EM 3.4 though (it worked with Hibernate EM 3.5.1, i.e. the JPA 2.0 implementation). Maybe I did something wrong.
Anyway, using a shared primary key seems to provide a valid solution.
Yes that is possible.
Look at the following example using Driver and DriverId class as id for Driver.
#Entity
public class Drivers {
private DriversId id; //The ID which is located in another class
public Drivers() {
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "personId", column = #Column(name = "person_id", nullable = false))})
#NotNull
public DriversId getId() {
return this.id;
}
//rest of class
}
Here we are using personId as the id for Driver
And the DriversId class:
//composite-id class must implement Serializable
#Embeddable
public class DriversId implements java.io.Serializable {
private static final long serialVersionUID = 462977040679573718L;
private int personId;
public DriversId() {
}
public DriversId(int personId) {
this.personId = personId;
}
#Column(name = "person_id", nullable = false)
public int getPersonId() {
return this.personId;
}
public void setPersonId(int personId) {
this.personId = personId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof DriversId))
return false;
DriversId castOther = (DriversId) other;
return (this.getPersonId() == castOther.getPersonId());
}
public int hashCode() {
int result = 17;
result = 37 * result + this.getPersonId();
return result;
}
}
You can do this by sharing a primary key between EntityFoo and EntityBar:
#Entity
public class EntityBar
{
#Id #OneToOne
#JoinColumn(name = "foo_id")
EntityFoo foo;
// other stuff
}
#Entity
public class EntityFoo
{
#Id #GeneratedValue
Integer id;
// other stuff
}
You have to use #EmbeddedId instead of #Id here.
And EntityFoo should be Embeddable.
Another way is to put an integer, and a OneToOne with updateble and instertable set to false.

Categories