I transform my site to use Java EE and hibernate!
I have 3 tables:
like
id | table | idElement | userId
movie
movie_id | title | duration
videoGame
game_id | title | nbPlayer
and currently 2 class:
#Entity
#Table(name="movie")
public class Movie {
#Id
#GeneratedValue
#Column(name="movie_id")
private Integer id;
private String title;
private int duration;
#Entity
#Table(name="videoGame")
public class Game {
#Id
#GeneratedValue
#Column(name="game_id")
private Integer id;
private String title;
private int nbPlayer;
I do not know how to make my table "like" to link it to one of the two table and the correct id?
example:
table like:
+---+-------+-----------+-------+
|id | table | idElement | userId|
|1 | movie | 1 | 1 |
|1 | game | 5 | 3 |
+---+-------+-----------+-------+
thanks a lot for your help!
Looks like there is no direct way to map your tables to hibernate (At least I am not aware of).
Using inheritance you can produce a similar effect. Movie and Game can be a subclass of another class (say Likable). So that you can use Likable class like below:
#Entity
#Table(name="like")
public class Like {
#Id
#GeneratedValue
#Column(name="like_id")
private Integer id;
#ManyToOne
#JoinColumn(name="likable_id", nullable=false)
private Likable likable;
#ManyToOne
#JoinColumn(name="user_id", nullable=false)
private User user;
}
Likable class will look like below:
#Entity
#Table(name = "likable")
#Inheritance(strategy=InheritanceType.JOINED)
public class Likable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private int id;
}
Movie class:
#Entity
#Table(name="movie")
#PrimaryKeyJoinColumn(name="id")
public class Movie extends Likable {
private String title;
private int duration;
}
And Game class:
#Entity
#Table(name="videoGame")
#PrimaryKeyJoinColumn(name="id")
public class Game extends Likable {
private String title;
private int nbPlayer;
}
Related
Preamble An Oracle DB read-only(I don't have access) has the following two tables:
person
person table
| id | name | gender |
| -- | ------ | ------ |
| 2001 | Moses | M |
| 2002 | Luke | M |
| 2003 | Maryam | F |
PK(id)
reference
reference table
| sep | guid | table_name |
| --- | -------- | ---------- |
| 2001 | EA48-... | person |
| 2002 | 047F-... | person |
| 2003 | B23F-... | person |
| 2003 | 3E3H-... | address |
| 2001 | H2E0-... | address |
| 2001 | 92E4-... | report |
No PK, it is generated by some triggers
The person table is a straight forward table with a primary key. The reference table are generated via a trigger that stores the id(PK) in sep column of any table and the table name that is store in table_name column (Note: Since no primary key, the reference table stores duplicate values in the sep column but distinct value into guid.)
Requirement
I need to use JPA to get the record from the reference table and map to the person record (person.id and other table.id are stored in reference.sep column) using Jackson as follows
{
"id": 2001,
"name": "Moses",
"gender": "M",
"reference": {
"sep": 2001,
"guid": "EA48-...",
"tableName": "person"
}
}
Entity (Person)
#Entity
#Table(name="person")
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "sep", insertable = false, updatable = false)
private Reference reference;
// Getters & Setters
}
Entity (Reference)
#Entity
#Table(name="reference")
public class Reference implements Serializable {
private Long sep;
private String guid;
private String tableName;
//Getters & Setters
}
Problem 1
JPA throws error of no #Id annotation on Reference table.
Problem 2
If I add the #Id annotation on the sep field, JPA throws error of duplicate values for that column.
Problem 3
If I add the #Id annotation on the guid field (it is unique field), JPA throws error of mapping a Long to a String field (org.hibernate.TypeMismatchException: Provided id of the wrong type for class)
Question
How can I structure the entities (Person.java and Reference.java) in order to come up with the output below:
{
"id": 2001,
"name": "Moses",
"gender": "M",
"reference": {
"sep": 2001,
"guid": "EA48-...",
"tableName": "person"
}
}
Reference is the owner of the relationship and needs to be specified as such in either a unidirectional or bidirectional relationship
// Unidirection relationship
#Entity
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
// Getters & Setters
}
#Entity
public class Reference implements Serializable {
#Id
private String guid;
private String tableName;
#OneToOne
#JoinColumn(name = "sep", insertable = false, updatable = false)
private Person person;
//Getters & Setters
}
// Bidirection relationship
#Entity
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
#OneToOne(mappedBy = "person")
private Reference reference;
// Getters & Setters
}
#Entity
public class Reference implements Serializable {
#Id
private String guid;
private String tableName;
#OneToOne
#JoinColumn(name = "sep", insertable = false, updatable = false)
private Person person;
//Getters & Setters
}
Same example for read any kind records from table reference:
#Entity
#Table(name = "reference")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "table_name")
public abstract class AbstractReferenceEntity {
#Id
private UUID guid;
public UUID getGuid() {
return guid;
}
public void setGuid(UUID guid) {
this.guid = guid;
}
}
#Entity
#DiscriminatorValue("person")
public class PersonReferenceEntity extends AbstractReferenceEntity {
#OneToOne
#JoinColumn(name = "sep")
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
// Read all types of records.
AbstractReferenceEntity e = this.em.find(AbstractReferenceEntity.class, sameUuid));
// Read only person type of records.
AbstractReferenceEntity e = this.em.find(PersonReferenceEntity, sameUuid);
For the benefit of anyone looking to solve this kind of issue, I will be posting the solution that works for me following #XtremeBaumer suggestion in the comment.
Step 1: For the REFERENCE table, I made the JPA entity to have two ids (sep & table_name) by creating an extra composite Id class and using it in the Reference Entity.
public class RefId {
private Long sep;
private String tableName;
//All args constructor
//No args constructor
//Setters & Getters
//Override the equals() and hashCode() !very important
}
Step 2: Add the above class as a composite id to the Reference entity by using the #IdClass annotation. We must also declare and annotate the two fields with #Id in the Reference class.
#Entity
#Table(name="reference")
#IdClass(RefId.class) // Important if not using embeddable type
public class Reference implements Serializable {
#Id
private Long sep;
private String guid;
#Id
private String tableName;
//Getters & Setters
}
Step 3: In the Person entity, declare #OneToOne on the Reference entity and annotate it with #JoinColumnsOrFormulas as shown below:
#Entity
#Table(name="person")
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
#OneToOne
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(column = #JoinColumn(name = "id", referencedColumnName = "sep", insertable = false, updatable = false)),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'person'", referencedColumnName = "tableName"))
})
private Reference reference;
// Getters & Setters
}
This works fine in the scenario above. Note in the formula = #JoinFormula, it is like we are declaring the 'WHERE' clause i.e. WHERE table_name = 'person' (Don't miss the single quotes)
Lastly, by using the Jackson object mapper, I was able to get
{
"id": 2001,
"name": "Moses",
"gender": "M",
"reference": {
"sep": 2001,
"guid": "EA48-...",
"tableName": "person"
}
}
Thanks for your insight (#XtremeBaumer)
I'm studying spring boot + PostgreSQL and I have a question.
I have a class which is an entity, it's called order and creates a table in PostgreSQL, it has three attributes: private long orderid, private String productname and private Seller seller;
The last one, Seller is another class/entity, it has the attributes sellername and productcode, how can I pass this class Seller to my class Order? I want to do that in order to create a table in PostgreSQL.
#Entity
#Table(name ="order")
public class Order implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderid;
#Column(name="productname")
private String product name;
**Here is where I want to put the class Seller**
}
#Entity
#Table(name="seller")
public class Seller implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="productcode")
private String productcode;
#Column(name="sellername")
private String sellername;
Whenever there is need for nested structure in Postgresql , we create type.
Step 1: Create type Seller:
postgres=# create type seller as (sellername varchar(20),productcode int);
CREATE TYPE
Step 2: Create table Order ( table name ORDER is under double-quote because order is a keyword in postgresql):
postgres=# create table "order"(orderid bigint,productname varchar(20),seller seller);
CREATE TABLE
postgres=# \d "order"
Table "public.order"
Column | Type | Collation | Nullable | Default
-------------+-----------------------+-----------+----------+---------
orderid | bigint | | |
productname | character varying(20) | | |
seller | seller | |
Step 3: Insert and verify values:
postgres=# insert into "order" values(1,'laptop',('roger',10));
INSERT 0 1
postgres=# select * from "order";
orderid | productname | seller
---------+-------------+------------
1 | laptop | (roger,10)
(1 row)
Step 4: Querying composite type:
postgres=# select * from "order";
orderid | productname | seller
---------+-------------+------------
1 | laptop | (roger,10)
(1 row)
postgres=# select seller from "order" where orderid=1;
seller
------------
(roger,10)
(1 row)
postgres=# select to_json(seller) from "order" where orderid=1;
to_json
-----------------------------------------
{"sellername":"roger","productcode":10}
(1 row)
postgres=# select to_json(seller)->>'sellername' from "order" where orderid=1;
?column?
----------
roger
(1 row)
postgres=# select to_json(seller)->>'productcode' from "order" where orderid=1;
?column?
----------
10
(1 row)
I think you need to have relationship #OneToMany for saller -> order and #ManyToOne order -> saler. In this case will be like this.
#Entity
#Table(name ="order")
public class Order implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderid;
#Column(name="productname")
private String product name;
#ManyToOne
#JoinColumn(name = "seller_id)
private Seller seller;
}
#Entity
#Table(name="seller")
public class Seller implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="productcode")
private String productcode;
#Column(name="sellername")
private String sellername;
#OneToMany(mappedBy = "seller")
private List<Order> orders;
}
In this case you will have the right relationship, so One seller can have many orders.
I have created four entity classes as:
#Entity
#Table(name = "DashboardRegionCountry")
public class DashboardRegionCountry implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "dashboardId")
private long dashboardId;
#OneToOne(targetEntity = Country.class)
#JoinColumn(name="countryId")
private Country country;
#OneToOne(targetEntity = Region.class)
#JoinColumn(name="regionId")
private Region region;
#ManyToOne()
#JoinColumn(name="dashboardId")
private Dashboard dashboard;
}
#Entity
#Table(name = "Dashboard")
public class Dashboard implements Serializable {
#Id
#Column(name = "dashboardId")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long dashboardId;
#Column(name = "dashboardName")
private long dashboardName;
#OneToMany(mappedBy= dashboard)
private List<DashboardRegionCountry> dashboardRegionCountry;
}
#Entity
#Table(name = "Country")
public class Country implements Serializable {
#Id
#Column(name = "countryId")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long countryId;
#Column(name = "shortName")
private String shortName;
#Column(name = "longName")
private String longName;
}
#Entity
#Table(name = "Region")
public class Region implements Serializable {
#Id
#Column(name = "regionId")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long regionId;
#Column(name = "shortName")
private String shortName;
#Column(name = "longName")
private String longName;
}
And the table schemas for the respective entities are as follows:
DashboardRegionCountry:
+----------------+---------------+
| Field | Type |
+----------------+---------------+
| id(PK) | Number(11) |
| dashboardId(FK)| Number(11) |
| countryId | Number(11) |
| regionId | Number(11) |
+-------------+------------------+
Dashboard:
+----------------+---------------+
| Field | Type |
+----------------+---------------+
| dashboardId(PK)| Number(11) |
| dashboardName | varchar(11) |
+-------------+------------------+
Country:
+-------------+---------------+
| Field | Type |
+-------------+---------------+
| countryId(PK)| Number(11) |
| shortName | Varchar2(10) |
| longName | Varchar2(10) |
+-------------+---------------+
Region:
+-------------+---------------+
| Field | Type |
+-------------+---------------+
| regionId(PK)| Number(11) |
| shortName | Varchar2(10 |
| longName | Varchar2(10) |
+-------------+---------------+
Basically, when user enters the dashboardId then we want to fetch, dashboardDetails along with the Region and respective countries presnt in that region. As stated above, I only have region and country Ids in my table and their names are present in other tables.
I want to display my sample output something like:
{
"dashboardId":20,
"DashboardRegionCountry": [{
"Region":"ASIA",
"dashboardId":["India","China"]
},
{
"Region":"NAM",
"dashboardId":["USA","Canada"]
}
]
}
I am trying to write JPA repository but was wondering is it possible to write something like:
#Repository
public interface DashboardRegionCountryRepository extends JpaRepository<DashboardRegionCountry, Long>{
List<Map<Long,Country>> findRegionBy_RegionId(Long dashboardId);
}
I am trying to fetch all the data in one query, any suggestion will be really helpful
Just get the corresponding DashboardRegionCountry using getById (check the reference documentation) and it will contain both associated Country and Region. If you then don't want to expose all Country and Region information in your entities I suggest you map them to a DTO that would be the model that you want to use while returning something on your controllers.
findByRegion_regionId try this this will work.
I am working on a project with Spring Boot (2.2.6.RELEASE) and JPA, and I am facing an issue that is bringing me nightmares...
I have a weak entity whose PK is a composite key of primary keys from parent tables.
+-----------------+
| BAR |
+-----------------+
| BAR_ID PK |
| NAME |
+-----------------+
|
| +-----------------+
| | FOO |
+------<+-----------------+
+------<| BAR_ID PK FK |
| | BAZ_ID PK FK |
| +-----------------+
|
+-----------------+
| BAZ |
+-----------------+
| BAZ_ID PK |
| NAME |
+-----------------+
So I have the following mapping...
#Entity
#Table(name = "FOO")
public class Foo implements Serializable {
#Data
#Embeddable
public static class ID implements Serializable {
#ManyToOne
#JoinColumn(name="BAR_ID")
private Bar bar;
#ManyToOne
#JoinColumn(name="BAZ_ID")
private Baz baz;
}
#EmbeddedId
private ID id;
}
#Entity
#Table(name = "BAR")
#SequenceGenerator(name = "SEQ_BAR", sequenceName = "SEQ_BAR")
public class Bar implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "SEQ_BAR")
#Column(name = "BAR_ID")
private Long id;
#Column(name = "NAME")
private String name;
}
#Entity
#Table(name = "BAZ")
#SequenceGenerator(name = "SEQ_BAZ", sequenceName = "SEQ_BAZ")
public class Baz implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "SEQ_BAZ")
#Column(name = "BAZ_ID")
private Long id;
#Column(name = "NAME")
private String name;
}
and the following Repository interface:
#Repository
public interface FooRepository extends JpaRepository<Foo, Foo.ID> {
}
then I try doing findById
fooRepository.findById(new Foo.ID(new Bar(1), new Baz(1));
and got the problem:
When I call fooRepository.findById(), although the row is fetched from database, ManyToOne mappings for Bar e Baz are not working properly. That means, whether I do foo.getId().getBar().getName() or foo.getId().getBaz().getName(), attribute name is null for both Bar and Baz objects (of course, the atrributes are not null in the database).
So my questions are:
This should work, right?
What am I doing wrong?
I have already tried changing fetch type to EAGER, but that didn't work at all.
I really appreciate any replies.
When I print database execution statement,the statement is Hibernate: select foo0_.bar_id as bar_id2_2_0_, foo0_.baz_id as baz_id1_2_0_ from foo foo0_ where foo0_.bar_id=? and foo0_.baz_id=?.Obviously the JPA does not query the Bar and Baz tables,so foo.getBar().getName() certainly will be null.
You can use follow way to achieve the same effect.
#Entity
#Table(name = "FOO")
#Data
public class Foo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#ManyToOne
#JoinColumn(name="BAR_ID")
private Bar bar;
#ManyToOne
#JoinColumn(name="BAZ_ID")
private Baz baz;
}
#Repository
public interface FooRepository extends JpaRepository<Foo, Long> {
Foo findByBarAndBaz(Bar bar, Baz baz);
}
use it like this:
Foo byId = fooRepository.findByBarAndBaz(new Bar(1L), new Baz(1L));
Bar bar = byId.getBar();
System.out.println(bar.toString());
I have 3 tables like this in MySQL 5:
| PERSON |
| id | fullName | isEmp | isParent |
| EMPLOYEE |
| personId | code |
| PARENT |
| personId | job |
in which, Employee.personId and Parent.personId are foreign keys pointing to Person.id. An employee can also be a parent and vice versa. So how can I config using Annotation of JPA 2.0/Hibernate 3? Thanks!
If a Person can be both, you can't solve this through inheritance, because Java doesn't allow multiple inheritance. So you'll have to go with Aggregation, which is confusing on a semantic level, because it's has-a-parent instead of is-a-parent. But I'm afraid it's the way you'll have to go:
#Entity
public class Person{
#Id
private Long id;
#OneToOne(optional=true)
private Employee employee;
#OneToOne(optional=true)
private Parent parent;
public boolean isParent(){return parent!=null;}
public boolean isEmployee(){return employee!=null;}
}
#Entity
public class Employee{
#Id
private Long id;
#OneToOne(mappedBy="employee",optional=false)
private Person person;
}
#Entity
public class Parent{
#Id
private Long id;
#OneToOne(mappedBy="parent",optional=false)
private Person person;
}
(getters / setters etc. omitted)