JPA - Secondary Table mapping direction - java

I've got two tables:
CREATE TABLE Checkin (
ID int primary key,
foo varchar(100),
bar varchar(100),
FK_type int foreign key references Type(ID)
)
CREATE TABLE Type (
ID int primary key,
type varchar(100)
)
Since the secondary table only stores labels, I'd like to map the values directly into my entity. I figured it should be possible using #SecondaryTable...
#Table(name = "Checkin")
#SecondaryTable(name = "Type",
pkJoinColumns = #PrimaryKeyJoinColumn(name="FK_type", referencedColumnName = "ID")
)
class Checkin {
#Id
private Integer id;
private String foo;
private String bar;
#Column(name="FK_type", table="Type")
private String type;
}
Unforunately, it would seem that the #SecondaryTable annotation works the other way around, meaning it wants my actual primary data table with the most columns to be the one joining. So I get thrown the error
Invalid column name 'FK_type'.
Is there a way to solve that through different annotations or do I really need to build the whole construct the other way round and have the main entity refer to "Type" and "Chekin" be the secondary table?

You should join Type entity in Checkin:
class Checkin {
#Id
private Integer id;
private String foo;
private String bar;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "type_id", referencedColumnName = "id")
private Type type;
}

Try to correct this:
#Column(name="FK_type", table="Type")
private String type;
to this:
#Column(name="type", table="Type")
private String type;
The table Type just do not have the FK_type column, as I understand you want to use Type.type here.
P.S. You can omit to use referencedColumnName if this is a reference to the PK.

Related

JPA/Hibernate Spring boot-primary key one entity referred as an instance to other entity not working

I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.

How to get additional column from different table with Spring Data?

So lets imagine following situation. I have an entity such as this:
#Entity
public class Price {
#Id
private int id;
#Column
private int amount;
private String currency;
}
And I have two tables:
CREATE TABLE currency (
id integer not null primary key,
name varchar
);
CREATE TABLE price (
id integer not null primary key,
amount integer,
currency_id integer references currency(id)
);
I want to tell Spring that when I access Price.getCurrency() I want to have whatever is stored in column "name" of the "currency" table. In other words, I want to connect two tables in one entity.
I can make currency a separate class, annotate the property with #OneTo... and get it like price.getCurrency().getName(). But I don't want a separate class, I just need this specific column.
I tried adding it via #SecondaryTable annotation like this:
#SecondaryTable(name = "currency",
pkJoinColumns = #PrimaryKeyJoinColumn(name = "id", referencedColumnName = "currency_id"))
But in this case Spring connect two tables by it's ids like this:
SELECT * FROM price LEFT JOIN price ON price.id = currency.id
And of course it is not working. So how do I do this? Is #SecondaryTable a correct way and if so how do I connect it through non-primary key column?
Yes, you can use #SecondaryTable:
#Entity
#Table(name = "price")
#SecondaryTable(
name = "currency",
pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "currency_id")
})
public class Price {
#Id
private int id;
#Column
private int amount;
#Column(table = "currency", name = "name")
private String currency;
}

How do I properly map entities where a primary key is composed of two foreign keys, one of which is composite itself?

I'm having a hard time trying to figure out how to properly do ORM on a certain database design.
My schema consists of three tables: a user table, a review table, and a vote table. Users can publish reviews for albums, and they can also assign a positive or negative rating to any review. Albums are provided from an external API, so their table is missing from the schema, but their IDs are referenced.
A user primary key simply consists of their username. A review primary key is composed of the reviewer's username, which is a foreign key, and the reviewed album ID. Finally, a vote primary key is composed of the voter's username, again a foreign key, and the voted review's primary key, consisting, as said earlier, of the reviewer's username and the reviewed album ID.
A user can publish a review for each individual album, and also can assign a vote for each individual review.
This is the ER model representing the schema:
To map the entities IDs, I'm using the #IdClass annotation, but I'm not sure I'm headed in the right direction. I also tried using the #EmbeddedId annotation, but the result is the same.
This is what my entities classes look like so far:
#Entity
public class User implements Serializable {
private static final long serialVersionUID = 1;
#Id #Column(name = "username")
private String username;
#Column(unique = true, nullable = false)
private String email;
#Column(name = "password", nullable = false)
private String password;
#Temporal(TemporalType.TIMESTAMP) #Column(name="signUpDate", nullable = false)
private Date signUpDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(ReviewId.class)
public class Review implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "reviewerUsername", referencedColumnName = "username")
private User reviewer;
#Id #Column(name = "reviewedAlbumId")
private Long reviewedAlbumId;
#Column(name = "content", nullable = false, length = 2500)
private String content;
#Column(name = "rating", nullable = false)
private Integer rating;
#Temporal(TemporalType.TIMESTAMP) #Column(name = "publicationDate", nullable = false)
private Date publicationDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(VoteId.class)
public class Vote implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "voterUsername", referencedColumnName = "username")
private User voter;
#Id #ManyToOne #JoinColumns({
#JoinColumn(name = "reviewerUsername", referencedColumnName = "reviewerUsername"),
#JoinColumn(name = "reviewedAlbumId", referencedColumnName = "reviewedAlbumId")
})
private Review review;
#Column(name = "vote") // #todo add attribute nullable = false
private Boolean vote;
// empty constructor, getters, setters, equals and hashCode implementations
}
These are my ID classes:
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private User reviewer;
private Long reviewedAlbumId;
// empty constructor, getters, setters, equals and hashCode implementations
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private User voter;
private Review review;
// empty constructor, getters, setters, equals and hashCode implementations
}
And here is the content of the MySQL script used to generate the schema:
DROP SCHEMA IF EXISTS albumReviewsDatabase;
CREATE SCHEMA albumReviewsDatabase;
USE albumReviewsDatabase;
CREATE TABLE user (
username VARCHAR(20) PRIMARY KEY,
email VARCHAR(254) NOT NULL UNIQUE,
password CHAR(60) NOT NULL,
signUpDate TIMESTAMP NOT NULL DEFAULT now()
) ENGINE = INNODB;
CREATE TABLE review (
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
content TEXT NOT NULL,
rating SMALLINT UNSIGNED NOT NULL,
publicationDate TIMESTAMP NOT NULL DEFAULT now(),
CHECK (rating >= 0 AND rating <= 10),
PRIMARY KEY (reviewerUsername, reviewedAlbumId),
FOREIGN KEY (reviewerUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
CREATE TABLE vote (
voterUsername VARCHAR(20) NOT NULL,
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
vote BOOLEAN NOT NULL,
PRIMARY KEY (voterUsername, reviewerUsername, reviewedAlbumId),
FOREIGN KEY (voterUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (reviewerUsername, reviewedAlbumId) REFERENCES review(reviewerUsername, reviewedAlbumId)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
I'm currently using OpenJPA as the persistence provider on a TomEE webprofile instance, and the used JPA version is 2.0.
Clearly I am misunderstating something about JPA's ORM, because when I deploy my application containing those entities I get the following exception:
<openjpa-2.4.2-r422266:1777108 fatal user error> org.apache.openjpa.util.MetaDataException: The id class specified by type "class application.model.Review" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "reviewer"
The exception is thrown because of the Review class mapping, and not the Vote class; however, I am sure that by solving the issue on the Review class, the same will reappear for Vote.
I'd prefer to get away with using the #IdClass annotation instead of #EmbeddedId, but whichever of the two I will end up using is not an issue.
These relationships are "derived identities"; so your ID classes should look like this (note the types of the foreign key fields differ from the types of their corresponding entity fields):
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private String reviewer; // matches name of #Id attribute and type of User PK
private Long reviewedAlbumId;
// ...
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private String voter; // matches name of #Id attribute and type of User PK
private ReviewId review; // matches name of #Id attribute and type of Review PK
// ...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
Also, as a side note, #IdClass is a bit Old School while #EmbeddedId is cleaner, eliminating the code duplicated across the entity and its key.

JPA: How can an #Embeddable object get a reference back to its owner, but the #Embeddable is in a lazy collection?

I know that if you want to reference back from #Embeddable to its parent you can set the parent "manually" in the setter and use #Access(AccessType.PROPERTY) for this embedded field as stated in this answer, but what if this embedded element is mapped in a collection, which is lazy loaded?
Actually not sure whether this is an issue, if not "manually" reference back from #embeddable to its parent, everything is fine.
#CollectionTable.JoinColumns() is used to set the foreign key columns of the collection table which reference the primary table of the entity, which means that once set this optional property, there is no necessary to "manually" reference back from #embeddable to its parent.
Use your case as example:
#Entity
public class Image {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "COMPUTERS", joinColumns = #JoinColumn(name = "ID_IMAGE"))
private List<Computer> computers;
}
#Embeddable
public class Computer {
#Column
private String ipAddress;
*****//This idImage field is not necessary
#Column(name = "ID_IMAGE", insertable = false, updatable = false)
private Long idImage;*****
}
Once comment out the field idImage and its #Column annotation, the generated SQL is:
create table IMAGES (
id bigint not null,
Name_Image varchar(255),
primary key (id)
)
create table COMPUTERS (
ID_IMAGE bigint not null,
ipAddress varchar(255)
)
alter table COMPUTERS
add constraint FKl1ucm93ttye8p8i9s5cgrurh
foreign key (ID_IMAGE)
references IMAGES
If "manually" declare the join column in the embeddable class, although the DDL are the same, the embeddable object will contain one extra field "imageId", which will cause the JDBC call parameter out of index when executing the INSERT operation.

Hibernate JPA, one to one relationship with a composite key and a constant value

I'm attempting to implement a limited type of object level ACL and its lead me to a place where I'm attempting to create a #OneToOne relationship using a composite key with a constant and dynamic value.
I have an Entity with a database id and a constant value defined in the class.
public class Entity{
private static final int objectType = 1;
#Id
Integer id;
}
I have an access_levels table with a composite key of objectId and objectType.
public class AccessLevel {
#EmbeddedId
private AccessLevelKey accessLevelKey;
#Embeddable
class AccessLevelKey implements Serializable{
private Integer objectType;
private Integer objectId;
....
}
}
Schema of access_levels
CREATE TABLE access_levels(
object_type INTEGER NOT NULL,
object_id INTEGER NOT NULL,
....
CONSTRAINT access_levels_type_id PRIMARY KEY (object_type, object_id)
);
I'm attempting to come up with a one to one relationship that Entity can use to fetch and update its associated AccessLevel
After taking a look a the docs on Non-Standard Joins it seems like I need something like this,
Inside of Entity:
#OneToOne
#JoinColumns({
#JoinColumn(name = "id", referencedColumnName = "object_id"),
#JoinColumn(name = "access_levels.object_type", referencedColumnName = "1"),
})
private AccessLevel accessLevel;
However this throws a hibernate MappingException at app launch
Caused by: org.hibernate.MappingException: Unable to find column with logical name: 1 in access_levels
Thanks!

Categories

Resources