I have been trying to figure out how to do this for sometime without any luck and have not managed to find anything useful while search on Google either.
I have THREE tables:
HOTEL
- id
- name
- local_id (foreign key)
DESCRIPTION
- id
- description
- hotel_id (foreign key)
- locale_id (foreign key)
LOCALE
- id
- local
I also have the following HOTEL DAO model:
#Entity
#Table(name = "HOTEL")
public class Hotel implements Serializable {
#Column(name = "id")
private long id;
#Column(name = "description")
private HotelDescription description;
}
Using JPA, how can I retrieve the data from table DESCRIPTION based on hotel_id and locale_id to populate description in DAO model hotel?
Well, you also have HotelDescription JPA entity, right? So you can define bidirectional mapping for entities.
instead of
#Column(name = "description")
private HotelDescription description;
you should have something like
#OneToOne(mappedBy = "hotel", cascade = CascadeType.ALL)
private HotelDescription desc;
and on the other side, in HotelDescription you should have back mapping
#OneToOne
#JoinColumn(name = "hotel_id")
private Hotel hotel;
When you will extract Hotel entity, JPA will also fetch child entity (HotelDescription) for you.
if you want to use #OneToMany mapping it will be (many descriptions for one hotel)
#OneToMany(mappedBy = "hotel", cascade = CascadeType.ALL)
private HotelDescription desc;
and on the other side
#ManyToOne
#JoinColumn(name = "hotel_id")
private Hotel hotel;
In JPA you can use several types of mapping like OneToMany, ManyToMany... That's only basics. Find a tutorial. You may start here: http://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html (not the best one probably)
Oh. And make sure you annotate id with #Id
I would consider ditching the Locale table and working with java.util.Locale directly. Hibernate (not sure about other JPA implementations) has auto type conversion from char column to java.util.Locale. This would then look something like:
DESCRIPTION
- id
- description
- hotel_id (foreign key)
- locale
And Entity:
import java.util.Locale;
#Entity
#Table(name = "HOTEL")
public class Hotel implements Serializable {
#Column(name = "id")
private long id;
#OneToMany
#JoinColumn(name = "holiday_id", nullable = false)
#MapKeyColumn(name = "locale_id")
private Map<Locale, HotelDescription> descriptions;
public String getDescriptionForLocale(Locale locale){
//try an exact match e.g. en_us
if(descriptions.containsKey(locale){
return descriptions.get(locale).getDescription();
}
//try language only e.g. en
else if (decsriptions.containsKey(locale.getLanguage){
return descriptions.get(locale.getlanguage()).getDescription();
}
//return a default or null
return ??
}
}
Related
we're using Postgres and JPA/Hibernate to import a lot of data on a biweekly basis (~50-100M rows per import). We're trying to partition our tables per import, which has us running into some Hibernate PK/FK column mapping problems. The setup is essentially this on the SQL side
CREATE TABLE row (
import_timestamp timestamp,
id uuid,
PRIMARY KEY (import_timestamp, id)
) PARTITION BY LIST (import_timestamp);
CREATE TABLE row_detail (
import_timestamp timestamp,
id uuid,
row_id uuid,
PRIMARY KEY(import_timestamp, id),
CONSTRAINT row_detail_row_fk FOREIGN KEY (row_id, import_timestamp) REFERENCES row (id, import_timestamp)
) PARTITION BY LIST (import_timestamp);
and this on the Java side:
#Entity(name = "row")
public class RowEntity {
#EmbeddedId
private PartitionedId id;
#OneToMany(cascade = ALL, mappedBy = "row")
private List<RowDetailEntity> details;
}
#Entity(name = "row_detail")
public class RowDetailEntity {
#EmbeddedId
private PartitionedId id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "row_id", referencedColumnName = "id"),
#JoinColumn(name = "importTimestamp", referencedColumnName = "importTimestamp")
})
private RowEntity row;
}
#Embeddable
public class PartitionedId implements Serializable {
private Instant importTimestamp;
private UUID id;
}
Hibernate then complains on boot that:
column: import_timestamp (should be mapped with insert="false" update="false")
I can silence that error by doing as it says, but that makes little sense, because I am forced to set insertable=false and updatable=false for both #JoinColumn()s, which would mean row_id isn't populated on insert.
I could go the #MapsId route, but only if I give the row_detail table a PK that includes all 3 properties (import_timestamp, id, row_id), and I don't really want or need that.
So the question is, how do I get Hibernate to understand my overlapping, but not entirely nested PK/FK?
I have 2 database tables Customer and Items with 1 -> many relation. To fetch data from database i am using the following query.
select customer.id, customer.name, items.itemName, items.itemPrice from testdb.customer INNER JOIN items ON items.customer_Id = customer.id
I have an entity class Customers
#Entity
public class Customer{
#Id
private int id;
#Column
private String name;
#Column
private String itemName;
#Column
private int itemPrice;
public Customer() {}
//Getter and setter are here
.......
}
in Service class i have the following code.
#GET #Path("/getCustomerInfo")
#Produces(MediaType.APPLICATION_JSON)
public List getCustomerInfo() {
CustomerDao dao = new CustomerDao();
return dao.getBuildingsCustomerInfo();
}
in my DAO class i have the following code
public List<Customer> getCustomerInfo(){
Session session = SessionUtil.getSession();
String queryString = "the above mentioned query";
List<Customer> customerInfo = session.createNativeQuery(queryString, Customer.class) ;
session.close();
return customerInfo;
}
I am getting the following JSON response from the service
[id:1, name:"Alfred", itemName:"jeans", itemprice:10],[id:1, name:"Alfred", itemName:"jeans", itemprice:10],[id:2, name:"James", itemName:"watch", itemPrice:20 ],[id:2, name:"James", itemName:"watch", itemPrice:20 ], [id:2, name:"James", itemName:"watch", itemPrice:20 ]
The number of results are 5 which is correct But 2nd result is a copy of 1st, 4th and 5th are copies of 3rd. In 2nd, 4th and 5th results the itemName and the itemPrice should be different.
if I use createSQLQuery(queryString); instead of createNativeQuery(queryString, Customer.class); I am getting the correct result but without entity attribut names.
[1, "Alfred", "jeans", 10],[1, "Alfred", "shirt", 15],[2, "James", "watch", 20], [2, "James", "coffee", 25], [2, "James", "drinks", 30]
I have seen number of articles but could not find the solution. I have to use createNativeQuery() not createSQLQuery() because I need to map the entity class attributes. Please let me know if i am doing something wrong.
Your data structure is wrong on the Java side and not corresponding to the database relation. In the relation you describe you need to have a list of items:
#Entity
public class Customer implements Serializable {
// ... the fields you have so far
// assuming the parent field on the other side is called customer
// you may also want to set the cascade and orphanRemoval properties of the annotation
#OneToMany(mappedBy = "customer")
#JsonManagedReference // assuming you're using Jackson databind JSON
private List<Item> items;
}
And on the Item side:
#Entity
public class Item implements Serializable {
#Id
private int id;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_Id")
private Customer customer;
}
Then if you really the JSON data strucutred that way, you need a third Entity class to use as a ResultSetMapping.
#Entity
#SqlResultSetMapping(
name = "CustomerItem",
entities = #EntityResult(entityClass = CustomerItem.class)
)
#NamedNativeQueries({
#NamedNativeQuery(
name = "CustomerItem.getAll",
resultSetMapping = "CustomerItem"
query = "select customer.id as cid, items.id as iid, customer.name,"
+ " items.itemName, items.itemPrice from testdb.customer INNER JOIN"
+ " items ON items.customer_Id = customer.id"
)
})
public class CustomerItem implements Serializable {
#Id
private int cid;
#Id
private int iid;
#Column
private String name;
#Column
private String itemName;
#Column
private int itemPrice;
... getters and setters
}
Then you can use the native query in named variant, which should offer some slight optimizations.
List<CustomerItem> lst = em.createNamedQuery("CustomerItem.getAll", CustomerItem.class)
.getResultList();
The use of #SqlResultSetMapping is so that the returned entities are not monitored for changes, but you can still use the defined entity for the result. I believe that by JPA specification it should also work without it, but in Hibernate it doesn't. Could be a bug, or a planned, but not implemented feature, or I could just be misinterpreting the JPA usage, but this workaround does work with Hibernate 5+.
Not sure about the exact reason behind duplicates but SELECT DISTINCT will solve your issue as it will take only distinct records.
Refer using-distinct-in-jpa
I solve this issue by using #SqlResultSetMapping
I have the following structure to store data for tracking devices (reduced complexity to simplify) in a MySQL database.
I use Hibernate to handle this data in a web application.
Tables structure
tag
id
name (string)
last_tag_detail_id (foreign key on tag_detail table)
tag_detail
id
tag_id (foreign key on tag table)
date (UTC date)
lat (gps latitude, may be null if no GPS position)
lng (gps longitude, may be null is no GPS position)
Problem
I have a requirement that when I retrieve a tag, I should get the last tag_detail (which I have thanks to the tag_detail_id foregin key).
Now I also have to get the most recent tag_detail with a valid GPS position (latitude and longitude != null).
Is it possible to do so with Hibernate annotations without having to add a last_tag_detail_gps_id foreign key in the tag table ?
For now I have the following mapping :
Tag class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#Column(name = "mac")
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.REFRESH })
#JoinColumn(name = "last_tag_detail")
private TagDetail lastTagDetail;
TagDetail class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = { CascadeType.REFRESH, CascadeType.MERGE })
#JoinColumn(name = "tag_id", nullable = false)
private Tag tag;
#Column(name = "lat")
private BigDecimal latitude;
#Column(name = "lng")
private BigDecimal longitude;
#Column(name = "data", nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime date;
Is it possible without adding a foreign key in the tag table to add a lastTagDetailWithGPS field in the Tag class, which would retrieve the last TagDetail by date which have a non null latitude and longitude ? If so, what would be the proper Jpa or Hibernate annotation to do so ?
Thank you
You say you wanted to:
retrieve the last TagDetail by date which have a non null latitude and
longitude
I don't think you need to add an additional foreign key to your tag table, since in your Tag entity you already have a reference to a TagDetail entity mapped as one-to-one. In your JPA query, you can just simply traverse to your TagDetail entity and check on the values of its properties: latitude, longitude and date.
Here's your JPA query:
SELECT td FROM Tag t INNER JOIN TagDetail td
WHERE td.date = :date AND
td.latitude IS NOT NULL AND
td.longitude IS NOT NULL
In my app, I have 2 tables, Settlement Result and Settlement State
Settlement Result contains some basic data like name, type etc.
SettlementState is the "many" side of relatioship with Settlement Result and consist of Settlement Result PK and Status as Many-To-One ID as PK and a date. Example data:
Settlement Result
------------------------------
ID| Name | Sth
------------------------------
1 | Some Name | Something more
2 | Name2 | more2
Settlement State
----------
Result's ID | StatusId | Date
------------------------------
1 | 1 | some date
1 | 2 | date
1 | 3 | date
2 | 1 | date
Now I wish to select with HQL/Plain SQL that rows from Settlement Result, that have for example Status Id == 3, but not any with higher ID.
There are few possible statuses:
Status ID | Desc
-----------------
1 | Created
2 | Confirmed
3 | Accepted
4 | Rejected
When we are creating SettlementResult, there's always some "workflow". Firstly, Result has status Created (ID 1). Then it can be Rejected(ID 4) or Confirmed (ID 2). Then again we can Accept it or Reject.
So one SettlementResult can have SettlementStates with Status ID of 1,2,4 (Created, Confirmed, but not Accepted=Rejected).
The problem is, that I want to select only those SettlementResults which have certain status (for examle Created)."
With HQL query like this:
Query query = session.createSQLQuery( "select distinct s from SettlementResult s join s.settlementStates states where states.settlementStatePK.status.statusId == 1" );
It returns every Settlement Result, even those with Statuses 1,2,3 (cause collection contains the one with ID equal to created).
Is it possible to select ONLY those Settlement Results which have ONLY certain status for example if we want Created[ID 1], we get all with Settlement State status of 1 only, without 2,3 or 4 status. When we choose to select those with status id 3, we can accept if it has Settlement State with status id = 1,2,3 but NOT 4. Some kind of max[status.id]?
#Entity
#Table(name = "SETTLEMENT_STATE")
public class SettlementState
{
#EmbeddedId
private SettlementStatePK settlementStatePK;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "STATUS_DTTM")
private Date statusDate;
}
#Embeddable
public class SettlementStatePK implements Serializable
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS_ID")
private Status status;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SETTLEMENT_RESULT_ID")
private SettlementResult settlementResult;
}
#Entity
#Table(name = "SETTLEMENT_RESULT")
public class SettlementResult implements Serializable
{
#Id
#Column(name = "SETTLEMENT_RESULT_ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS_ID")
private Status status;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MODEL_GROUP_ID")
private SettlementModelGroup settlementModelGroup;
#Column(name = "\"MODE\"")
private Integer mode;
#Column(name = "CREATED_DTTM")
private Date createdDate;
#Column(name = "DESCRIPTION")
private String description;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "settlementStatePK.settlementResult")
private List<SettlementState> settlementStates;
}
You have two options here:
If you see your query you are fetching SettlementResult objects
Query query = session.createSQLQuery( "select distinct s from SettlementResult s join s.settlementStates states where states.settlementStatePK.status.statusId == 1" );
Your query is perfect but once you have parent object SettlementResult and you access OneToMany collection settlementStates, hibernate will load all of them on the basis of SettlementResult P.K. (This is what you have observed as well).
So Option-1:
Go through Child to Parent
This means return SettlementState objects from your query as:
Query query = session.createSQLQuery( "select distinct states from SettlementResult s join s.settlementStates states where states.settlementStatePK.status.statusId == 1" );
And then you can access SettlementResult from state object, because you have defined ManyToOne for SettlementResult in SettlementState class. In this case you will definitely have those SettlementResult objects what you are looking for.
Option-2: Divide your SettlementState objects
Option 1 will work for you but this solution might seem odd to you. So the best way to resolve this problem is you can divide Settlement State objects (as described in your problem)
1. RejectedSettlementState
2. NonRejectedSettlementState
These two classes will extend one base abstract class (SettlementState). You can define discriminator formula on status id. Once you have these classes then you can associate these subclasses into SettlementResult. Here are classes you need (sudo)
#DiscriminatorForumla("case when status_id == 4 then "REJECTEDSTATE" else "NONREJECTEDSTATE" end")
public abstract class SettlementState{
...
// Define all of your common fields in this parent class
}
#DiscriminatorValue("REJECTEDSTATE")
public class RejectedSettlementState extends SettlementState{
...
// define only fields specific to this rejected state
}
#DiscriminatorValue("NOTREJECTEDSTATE")
public class NonRejectedSettlementState extends SettlementState{
...
// define only fields specific to this non-rejected state
}
Now the SettlementResult class
public class SettlementResult{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "settlementStatePK.settlementResult")
private List<RejectedSettlementState> rejectedSettlementStates;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "settlementStatePK.settlementResult")
private List<NonRejectedSettlementState> nonRejectedSettlementStates;
}
So once you have all these objects. Then you don't need query on status. You simply load the parent object SettlementResult and then access your rejected or non-rejected settlement states. Hibernate will use formula condition to initialize these collections using lazy load as defined in SettlementResult class.
Note
Both solutions to me are acceptable, it depends which one you will like in your system to be there. Second option gives you more edges for future.
Any more info: please ask! I have tried my best to get you through this idea :). Good Luck!
The SchoolClass entity has a composite key made of schoolID, grade and section.
The key in student entity is a composite of userName and schoolID.
I am trying to add two foreign keys of student objects in my SchoolClass. I want to use the same column schoolID in SchoolClass table in all the mappings, am wondering if this is the correct way to do it or not ?
#Entity
public class SchoolClass {
#Column(unique=true, nullable=false)
#EmbeddedId
private SchoolClassPK key;
#ManyToOne(optional = true)
#JoinColumns({
#JoinColumn(name="classRep1", referencedColumnName="userName", nullable = true),
#JoinColumn(name="schoolID", referencedColumnName="schoolID", nullable = true),
})
private Student classRep1;
#ManyToOne(optional = true)
#JoinColumns({
#JoinColumn(name="classRep2", referencedColumnName="userName", nullable = true),
#JoinColumn(name="schoolID", referencedColumnName="schoolID", nullable = true),
})
private Student classRep2;
......
}
It is a better idea to simply remove composite keys in favor of an auto generated key on the entities involved.