I have an existing java application in which I use now spring-boot and data jpa to save some data in a database. In one class Order which I convert now to an #Entity I have a member which is a List<Position>. Following is the code of the reduced classes
#Entity
public class Order
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private List<Position> positions;
//some other members follow here...
}
#Entity
public class Position
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
//some members follow here...
}
So what I have done is the following, I added the annotation #Transient to my list in Order and add inPosition a reference to an Order:
#Entity
public class Order
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Transient
private List<Position> positions;
//some other members follow here...
}
#Entity
public class Position
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#ManyToOne
private Order order;
//some members follow here...
}
Now when I want to save an Order object, then I save first the Order in the corresponding repository and then go through the list of Positions and set in ervery the reference to Order and then save the Position object to its corresponding repository. If I want to fetch an Order then I fetch first the Order and then fetch the Positions in the correspoding repository with findByOrder(..).
So far this works. Now I'm facing the problem, if the application modifies in the Order the list with the Positions and I have to update the database with the new Order object, then I find no smooth solution to delete the Positions in the repository which were removed from the list by the application (as I have no longer a reference to the removed ones). I could delete first all Positions of that Order and then save the existing ones again.
So my questions is maybe if there is a better way to remove the Positions in the repository which were removed by the application. But maybe it would be an XY question, cause my approach how to save the Position-List is the reason why I am facing this problem. I appreciate any hints concering this.
You're not doing it right.
First, it's not clear why you're making the #OneToMany side #Transient.
Best is to use cascade features of JPA.
In your example, if you put:
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval=true)
private List<Position> positions;
All operations on Order will cascade on Positions aswell, so you don't need to explicitly manage them.
See these examples with Hibernate
Related
I am trying to build a to-do log keeper.
I am using java spring-boot with data-jpa which is built on hibernate.
I want a user to have several projects the user works on. Every project then has several tasks associated with it and the user tracks how much time was spent per a task by completing short atomic units of work (log entries).
So far I ended up building the most naive implementation of this system. It looked like several levels of one to many hierarchy: user->projects->tasks->entries. The current db implementation is based on a schema like this
Code for entity classes (getters setters constructors and some annotations are omitted for brevity):
#MappedSuperclass
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
#Entity
public class User extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Project> projects;
}
#Entity
public class Project extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "project", fetch = FetchType.LAZY)
private List<Task> tasks;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
}
#Entity
public class Task extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "task", fetch = FetchType.LAZY)
private List<Entry> entries;
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
}
#Entity
public class Entry extends AbstractEntity {
#Column
private Integer duration;
#Column
private LocalDateTime finish;
#ManyToOne
#JoinColumn(name = "task_id")
private Task task;
}
I want to be able to provide functionality for a user to view all the log entries in a user specified time frame. I added jpa repository like this:
public interface EntryRepository extends JpaRepository<Entry, Integer> {
#Query("SELECT e FROM Entry e WHERE (e.task.project.user.id=:user_id) AND " +
"(e.finish BETWEEN :from AND :to)")
List<Entry> getAllForUserInDateRange(#Param("from") LocalDateTime from,
#Param("to") LocalDateTime to,
#Param("user_id") int userId);
}
1) Is it correct to say that this query is inefficient? I was thinking performing a fetch like this from a database is inefficient because the query cannot take advantage of indexes. Since there is no foreign key user_id in the Entry table every row is being looked up and the chain entry->task->project->user is being followed. I end up with linear complexity instead of logarithmic.
2) What is a better way to solve the problem? Is it ok to store the foreign key to the user in the Entry table? If I will want to fetch entries from the database for a particular project or a task, then I will have to add foreign keys to these relationships as well. Is that ok?
You should check real SQL which is being executed. Set org.hibernate.SQL log level to DEBUG and you'll see the statements.
I think for your query you will actuall get three inner joins between four tables. You say the query cannot take advantage of indexes. It absolutely can. Create following indexes:
USER (ID)
PROJECT (USED_ID, ID)
TASK (PROJECT_ID, ID)
ENTRY(TASK_ID, ID)
See Contactenated Indexes from Use the Index, Luke.
With these indexes your joins across four tables will likely use indexes. I won't put my hand in fire for this, but it should work. Check the query plan.
You are right that the chain ENTRY->TASK->PROJECT->USER will be followed, but it should be quite faset with indixes
Your database schema is pretty normalized, which results in three joins across four tables. You could denormalize this schema by bringing, say, user_id to the ENTRY. This may improve query performance, but honestly I doubt this will bring much. You may want to run real-world benchmark before actually switching to this solution.
Using Hibernate, I need to query a MySQL database for a Post entity that has a one-to-one relationship with a Poll entity that has a one-to-many relationship with an Answer entity. I need the Post object to contain the Poll object and its Poll object to contain its Answer objects. Here's the basic class setup:
Update:
The Post table must not have a primary key column. It is a waste of data. I need to be able to get Post objects from the database using the user_id column. Getting Post objects using the user_id column is the only way it will ever be done, so it makes no sense for me to have a primary key column. So if you're going to provide an answer that provides insight into a solution that solves my problem, please keep those specifications in mind.
Post Class:
#Entity
#Table(name="user_feed")
public class Post implements Serializable {
//id for the user that is meant to receive the post
//*post object is taken from a table that will contain
//*posts for many different users
#Id
#Column(name="user_id")
private long mUserId;
//poll id
#Id
#Column(name="poll_id")
private long mPollId;
//boolean that indicates whether this post is a repost
#Column(name="is_repost")
private boolean mIsRepost;
//date the post was created
#Column(name="date_created")
private Date mDateCreated;
//the poll this post contains
#OneToOne
#JoinColumn(name="poll_id")
private Poll mPoll;
Poll Class:
#Entity
#Table(name="poll")
public class Poll implements Serializable{
//the poll's id
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
//id of the user who created the poll
#Column(name="user_id")
private long mUserId;
//the text of the poll's question
#Column(name="question")
private String mQuestion;
//the date the poll was created
#Column(name="date_created")
private Date mDateCreated;
//the answer objects for this poll
#OneToMany
#JoinColumn(name="id")
private List<Answer> mAnswers;
Answer Class:
#Entity
#Table(name="answer")
public class Answer implements Serializable {
//id for a particular answer
//*this is not a necessary value for the application logic, but
//*Hibernate forces me to designate an #Id annotation for every
//*entity, so I created this field and the associated column in
//*the database
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
//the answer's text
#Column(name="answer_text")
private String mAnswer;
//the id of the poll to which this answer pertains to
#Column(name="poll_id")
private long mPollId;
***I'm confused about the id for this table. It doesn't make sense for each answer to have a primary key, but Hibernate requires some sort of #Id annotation in the class, so I decided to just create a primary key column in the table for the sake of Hibernate. It's never used. I would like to get rid of it, but there really isn't anything that makes one Answer unique from another for the same poll except for their text at the moment -- it's not necessary for the application logic.
Query I came up with: doesn't work
.
This query was really just me testing to see if I could get a single Post object with all of its nested objects. I knew if I could get one, getting a collection wouldn't be much more of a stretch -- but I can't even get one.
Session session = HibernateUtilities.openSession();
session.beginTransaction();
//29 is a post meant for a particular user and 47 is the id of the
//poll that should be contained in the post
Post post = (Post)session.get(Post.class, new Post(29, 47));
session.getTransaction().commit();
session.close();
//suppose to return the post in JSON format to a client, but it
//doesn't work when I create the one-to-many relationship between
//the poll and it's answers. It only works without the relationship;
//which I've defined in the Poll class
return mGson.toJson(post);
You shouldn't put the primary keys of the relationships as fields of their own (e.g. you don't need both Post.mPoll and Post.mPollId, just use Post.mPoll.getId() if you need it). If I were to address your problem I would by default (we can discuss Post not having an id later) use the following object model (getters omitted for brevity but I would have them on all fields).
#Entity
public class Post {
#Id
#GeneratedValue
private long id;
#OneToOne
private Poll poll;
}
#Entity
public class Poll {
#Id
#GeneratedValue
private long id;
#OneToMany
private List<Answer> answers;
}
#Entity
public class Answer {
#Id
#GeneratedValue
private long id;
}
Start from there and see where it falls apart. If you want an entity to not have any ID then you can use the #Embedded, #Embeddable, and #ElementCollection annotations.
#Embeddable was originally meant for embedding "value" objects (e.g. things like currency, dates, postal addresses, etc.) and as such these objects do not need a primary key and are completely owned by their owning entity.
You reference the embeddable object with the #Embedded annotation (e.g. your User would have an #Embedded reference to the #Embeddable post if it were a one-to-one).
To reference a collection of embeddable objects you use the #ElementCollection annotation. However, members of an #ElementCollection are immutable (can't modify them in the database, have to remove it from the collection and add a new instance) and cannot be lazily loaded. Given the complexity of your Post object I would not personally make it an embedded class (you may want the ability to edit a post someday?) but if you want to it should work.
I say should because I have never had an embeddedable class that references other non-embeddable entities (e.g. your reference to the Poll). Give those things a try and if they don't work then please post exactly what is going wrong.
Solved it myself. All the comments in the below code designate the changes I made to the code I presented in the question and explain why I made them.
Post Class:
#Entity
#Table(name="user_feed")
public class Post implements Serializable {
#Id
#Column(name="user_id")
private long mUserId;
//removed long mPollId
//hibernate is capable of getting the foreign key for a post's
//poll_id column from its poll object -- mPoll
//so i don't have to have a separate field for the id of this post's
//poll
#Column(name="is_repost")
private boolean mIsRepost;
#Column(name="date_created")
private Date mDateCreated;
//made this field part of the composite id instead of long mPollId
//pretty much the same composite key as before just had to alter
//my implementation of Post.equals(Object) to use this poll's id
//instead of this class's mPollId field
//implementing your own .equals(Object) method is necessary when
//creating composite keys as i do with multiple #Id annotations
//i think you also have to implement your own .hashCode() method too
//but the word hash scares me, so I didn't do it
//the code works, so i'm just gonna let it rock
#OneToOne
#JoinColumn(name="poll_id")
private Poll mPoll;
Poll Class:
#Entity
#Table(name="poll")
public class Poll implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
#Column(name="user_id")
private long mUserId;
#Column(name="question")
private String mQuestion;
#Column(name="date_created")
private Date mDateCreated;
//removed #JoinColumn -- not completely sure about why it wasn't
//helping, but many of the examples similar to my use case didn't
//use it so I got rid of it
//added mappedBy variable -- still not really sure what it does
//but it works
//and added FetchType.EAGER so everytime a Poll object is loaded
//the answers it's associated with are loaded too
#OneToMany(mappedBy="mPoll", fetch=FetchType.EAGER)
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE})
private List<Answer> mAnswers;
Answer Class:
#Entity
#Table(name="answer")
public class Answer implements Serializable {
//turns out having a primary key on the answer table is actually useful
//for the application logic. would you look at that
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
#Column(name="answer_text")
private String mAnswer;
//got rid of long mPollId
//it was for the same reason i did in the Post class
//hibernate does the work for me with the mPoll object and the
//annotations i've provided on it
//made the relationship between a poll and its answers bidirectional
//not entirely sure how adding the below annotations to the new
//Poll field fixed my problems, but it did
//i imagine it somehow tells hibernate that the primary key
//for the below object is the foreign key represented by poll_id in the
//database table for this entity
//and making insertable=true enables hibernate to insert that foreign
//key into the appropriate column in the database when this entity
//is saved
//updatable seemed to be necessary
//hibernate complained when it wasn't there
//and nullable was in the helpful examples i found so it was copy and
//pasted along with the rest of the helpful stuff here
//this field can't be nullable anyways so semantically, it makes sense
//for it to be there
#ManyToOne
#JoinColumn(name="poll_id", nullable = false, insertable=true, updatable=false)
private Poll mPoll;
Final functioning query: does work
Session session = HibernateUtilities.openSession();
session.beginTransaction();
List<Post> usersFeed = session.createQuery("select p from Post p where p.mUserId = :userId")
.setString("userId", userId)
.list();
session.getTransaction().commit();
session.close();
I want to store a List<String> in a postgres DB.
#ElementCollection
private List<String> products;
Hibernate will therefore create a join table. Is it possible to prevent this?
One workaround would be to create an explicit class with bidirectional mapping as follows:
#Entity
public class MainEntity {
#OneToMany(mappedBy = "main")
private List<Product> products;
}
#Entity
public class Product {
#Id
private long id;
#ManyToOne
private MainEntity main;
private String text;
}
But I feel this is a bit over the top for just storing a string list, isn't it?
If you don't find anything better, try this:
Mark your List with #Transient so that it is never persisted directly.
Prepare additional field for persisting your list, of a type that is "persistable" directly by JPA (concatenated, delimetered String seems to be quite natural).
Use methods annotated with #PostLoad and #PrePersist to move data between those two fields, converting from List to String and back.
i'm not sure but could you remove :
#ManyToOne
private MainEntity main;
in class product.
I think it might works properly without this.
Do you want to handle your list from MainEntity or from Product?
I have a bean structure as shown below. The problem that I am facing is while trying to persist XBean, I am able to save all the data (i.e. xName, pBean, qBean, rBean, kBeans are all visible in storage) but there is no entry for Y_BEAN.
I am pretty much new with JPA annotations so not really sure if what I have done is correct. The idea is to have multiple entries of XBean (i.e. as List) with one instance of YBean
XBean also will hold an instance of YBean as its parent so when I retrieve XBean I should get all the data. Is there something wrong with #ManyToOne annotation?
#Entity
#Table (name = "X_BEAN")
public class XBean implements XInterface {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String xName;
#OneToOne(cascade=CascadeType.ALL)
private PBean pBean;
#ManyToOne
#JoinColumn(name="y_id")
private YBean yBean;
#OneToOne(cascade=CascadeType.ALL)
private qBean qBean;
#OneToOne(cascade=CascadeType.ALL)
private RBean rBean;
#OneToMany (mappedBy="xBean", cascade=CascadeType.ALL)
private List<KBean> kBeans;
// getter setters for each are below ...
}
and structure of YBean is like below
#Entity
#Table (name = "Y_BEAN")
public class YBean implements XInterface {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#OneToOne(cascade=CascadeType.ALL)
private ZBean zName;
#OneToOne(cascade=CascadeType.ALL)
private PBean pBean;
#OneToOne(cascade=CascadeType.ALL)
private RBean rBean;
#OneToMany (mappedBy="yBean", cascade=CascadeType.ALL)
private List<XBean> xBeans;
// getter setter for each are below ...
}
I am using Google App Engine's storage
You need cascade=CascadeType.PERSIST on your ManyToOne, to tell Hibernate to persist the YBean when it persists the XBean.
You should also think about whether you want the cascade attribute on the inverse OneToMany. With CascadeType.ALL, if you were to delete an instance of YBean, Hibernate will delete all associated XBeans (of which there may be zero, one, or many), because CascadeType.ALL means "apply persistence operations, including deletion, to any other entities accessible via this property or collection". If you didn't have CascadeType.ALL and you deleted a YBean that was referred to by one or more XBeans, then those XBeans would now referenced a non-existent YBean ID, so you'd probably need to do some cleanup in that case.
Both options are irrelevant if your business logic never deletes a YBean until it is not referred to by any XBeans, but if your business logic doesn't prevent the case, then you should cascade or not based on whether you want to get rid of the XBeans or whether you want to clean them up (but not delete them) to no longer refer to the YBean that's being deleted.
#ManyToOne
#JoinColumn(name="y_id")
private YBean yBean;
what is the column y_id ?
what is the definition of it?
you can try removing #JoinColumn(name="y_id") and let JPA handle it.
and also add fetch = FetchType.EAGER like this.
#OneToMany (mappedBy="yBean", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private List<XBean> xBeans;
I'm currently working on a project to persist data with JPA 2.1 and to search entities using hibernate search 4.5.0.final.
After mapping classes and indexing, the searching works fine.
However, when I changed the value description of classB from "someStr" to "anotherStr". The database was updated accordingly, but when I checked the index using Luke, classA.classB.description in the index wasn't updated, and the data cannot be searchable by keyword "anotherStr", but can be searchable by keyword "someStr".
After I reindex the whole database, it's updated finally.
According to Hibernate search website,
The short answer is that indexing is automatic: Hibernate Search will transparently index every entity persisted, updated or removed through Hibernate ORM. Its mission is to keep the index and your database in sync, allowing you to forget about this problem.
But it's not working in my case. I'm not sure if I missed some details or I need to handle it myself for this kind of issues.
I also tried to add annotation #Indexed on classB as suggested by this one, but it's still not solving my problem.
As far as I know, the solution would be to reindex the database periodically. But reindexing would disable the search functionality and that's not an option in most of the cases.
Could anyone give some suggestions? Thanks.
I have a class which embedded some other classes by using #IndexedEmbedded annotation. Here is a simplified version of my class mapping.
Class A
#Entity(name = "classA")
#Indexed
public class classA extends Model {
private int id;
private String name;
private ClassB place;
...
some constructors
...
#Id
#GeneratedValue
#DocumentId
public int getId() {
return id;
}
#Column(name = "name")
#Field(analyze = Analyze.NO, store = Store.YES) // only used for sorting
public String getName() {
return name;
}
#IndexedEmbedded
#ManyToOne
#JoinColumn(name = "place_id")
public ClassB getPlace() {
return place;
}
...
}
Class B
#Entity(name = "classB")
public class classB extends Model {
private int id;
private String description;
...
some constructors
...
#Id
#GeneratedValue
public int getId() {
return id;
}
#Fields({
#Field,
#Field(name = "description_sort", analyze = Analyze.NO, store = Store.YES)
})
#ContainedIn
#Column(name = "description")
public String getDescription() {
return description;
}
...
}
And the indexing methods is as follows:
fullTextEntityManager.createIndexer()
.purgeAllOnStart(true)
.optimizeAfterPurge(true)
.optimizeOnFinish(true)
.batchSizeToLoadObjects(25)
.threadsToLoadObjects(8)
.startAndWait();
You placed ContainedIn annotation incorrectly. According the Hibernate Search documentation:
Be careful. Because the data is denormalized in the Lucene index when using the #IndexedEmbedded technique, Hibernate Search needs to be aware of any change in the Place object and any change in the Address object to keep the index up to date. To make sure the Place Lucene document is updated when it's Address changes, you need to mark the other side of the bidirectional relationship with #ContainedIn.
In your example, you need to:
Make the relationship between classes bidirectional
Mark the relationship in ClassB as ContainedIn
In your case:
ClassB {
private Set<ClassA> linkedObjects;
....
#OneToMany(mappedBy="place")
#ContainedIn
public Set<ClassA> getLinkedObjects() {
return linkedObjects;
}
....
}
I had a similar problem but already with correct annotations. In my case, I have added forced flush both to the database and to index and refreshed it afterward:
myEm.flush();
Search.getFullTextEntityManager(myEm).flushToIndexes();
myEm.refresh(updatedObject);
hmmm, add #ContainedIn doesn't work for me.
I put the sample project here
https://github.com/yhjhoo/princeSSH
Update department object is not able to update person index