How to achieve Foreign key constraint in JPA + Hibernate - java

It's may day 5 with Hibernate. I'm learning JPA. My question is very much similar to:
JPA + Hibernate: How to define a constraint having ON DELETE CASCADE
But the problem is:
It was asked 8 years ago. So the versions will not match today.
The OP didn't mention the version of Hibernate or MySQL or Java.
There is no accepted answer.
I've two tables student and guide. They are linked with a foreign key:
I didn't create these tables. Hibernate created them for me because I'm using these annotations in my student class:
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name="guide_id")
private Guide guide;
It is clearly seen that Raj and Tanzeel have same guide i.e Joe. I want to maintain the referential integrity constraint. That means If I delete Raj then Joe will also get deleted. That means Tanzeel will be left without any guide. That should not happen right? But this is what I'm facing.
Here is my code:
Main.java
public class Main {
public static void main(String[] args) {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction txn = session.getTransaction();
try {
txn.begin();
// deleting Student[id=2L] (using CascadeType.REMOVE)
Student student = (Student) session.get(Student.class, 2L);
session.delete(student);
txn.commit();
} catch(Exception e) {
...
}
}
}
Student.java
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="enrollment_id", nullable=false)
private String enrollmentId;
private String name;
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name="guide_id")
private Guide guide;
public Student() {}
public Student(String enrollmentId, String name, Guide guide) {
this.enrollmentId = enrollmentId;
this.name = name;
this.guide = guide;
}
...
}
Guide.java
#Entity
public class Guide {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "staff_id", nullable = false)
private String staffId;
private String name;
private Integer salary;
public Guide() {}
public Guide(String staffId, String name, Integer salary) {
this.staffId = staffId;
this.name = name;
this.salary = salary;
}
}
Some info from my hibernate.cfg.xml
<session-factory>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">update</property>
<!-- Use Annotation-based mapping metadata -->
<mapping class="entity.Student"/>
<mapping class="entity.Guide"/>
</session-factory>
The problem is, when i deleted Raj, his guide Joe also got deleted and there was no exception seen regarding foreign key violation. Guide table has only Mike now and Tanzeel is referring to the guide Joe who doesn't even exists now.
My versions:
Java 11
mysql-connector-java 8.0.26
hibernate-core 5.5.6.Final
Please pitch in.

in your case, I would have used the bidirectional association instead of the unidirectional one.
So you're Guide entity would have seen like this,
#Entity
public class Guide {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy="guide")
private List<Student> students;
#Column(name = "staff_id", nullable = false)
private String staffId;
private String name;
private Integer salary;
public Guide() {}
public Guide(String staffId, String name, Integer salary) {
this.staffId = staffId;
this.name = name;
this.salary = salary;
}
}
I suggest you take a look at other examples on this page.
Hope it could help you.
--Edited--
I suggest you check this link, Thorben clarifies, why you shouldn't use CascadeType.REMOVE for -ToMany associations. In the end, you should remove the entity yourself using EntityManager or remove method.
And the above entity change of mine is irrelevant. Ignore it please.

CascadeType.REMOVE: It means that the related entities are deleted when the owning entity is deleted.
If you only have it mapped unidirectionally (Student to Guide), and you eliminate in cascade it is logical the behavior you are receiving.
Don't use cascade.REMOVE and add the other relation (Guide to Student) I think it would be the expected behavior.
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="enrollment_id", nullable=false)
private String enrollmentId;
private String name;
#ManyToOne()
#JoinColumn(name="guide_id")
private Guide guide;
}
#Entity
public class Guide {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "staff_id", nullable = false)
private String staffId;
private String name;
private Integer salary;
#OneToMany(mappedBy="guide")
private List<Student> students;
}

Related

PSQLException: ERROR: syntax error at or near

I have what I thought was a straight forward relation in JPA. Looks like this. CompanyGroup:
#Entity
#Table
public class CompanyGroup implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#JoinColumn(name = "companies")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Company> companies;
}
Company:
#Entity
#Table
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "name")
private String name;
#JoinColumn(name = "users")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<User> users;
#Id
#GeneratedValue
private Long id;
}
User:
#Entity
#Table
public class User {
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#Column(name = "email")
private String email;
#Id
#GeneratedValue
private Long id;
}
I have omitted setters, getters, etc.
This is not working. I'm trying to save a CompanyGroup(Has 2 companies, each company has 2 users, all entities are unique) to a fully empty database.
I persist this using Spring-Data, accessed in a service like this:
#Service
public class ConcreteCompanyGroupService implements CompanyGroupService {
#Autowired
private CompanyGroupRepository repository;
#Transactional
#Override
public void save(CompanyGroup group) {
repository.save(Collections.singleton(group));
}
}
When I try to call this method I receive this:
org.postgresql.util.PSQLException: ERROR: syntax error at or near "User"
Position: 13
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2458)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2158)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:291)
Hopefully I have done something stupid that someone can find quickly. I don't know how to solve this.
EDIT:
The driver in my pom.xml:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1211</version>
</dependency>
Your entity maps across to a table name that is an SQL reserved keyword (User). Sadly for you, your chosen JPA provider does not automatically quote the table name identifier, and so you get exceptions when referring to the table.
Solution is either to quote the table name yourself in the #Table annotation, or change the table name to not be a reserved keyword. Alternatively use a JPA provider that auto-quotes such reserved keywords for you (e.g DataNucleus)
Solution 1: As Pascal mentioned, you have to escape the table name with backslash like:
#Entity
#Table(name="\"User\"")
public class User {
...
}
Solution 2: Rename your table's anme with another name (Users)
#Entity
#Table(name="Users")
public class User {
...
}
Solution 3: Add a suffix to the table's name:
#Entity
#Table(name="APP_User")
public class User {
...
}
Solution 4: Change the entity name, e.g. ApplicationUser
#Entity
public class ApplicationUser {
...
}
The reason
PostgreSQL as some reserved SQL Key Words. For example: ABORT, ALL, ARRAY, CACHE, CUBE, USER, ... Those tokens are in the SQL standard or specific to PostgreSQL
Use the #Table annotation or change your class name from User to something else as User is a reserved keyword in sql.

How exactly does the Hibernate #OneToMany annotation work?

I am pretty new to Hibernate and I am studying it on a tutorial. I have some problems understanding how exactly the OneToMany annotation works.
So I have these 2 entity classes: Student that represents a student and Guide that represents a person that guides the student. So each student is associated with a single guide but a single guide can follow more that one student. I want a guide to know the students associated to him.
So I have:
Student:
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="enrollment_id", nullable=false)
private String enrollmentId;
private String name;
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name="guide_id")
private Guide guide;
public Student() {}
public Student(String enrollmentId, String name, Guide guide) {
this.enrollmentId = enrollmentId;
this.name = name;
this.guide = guide;
}
public Guide getGuide() {
return guide;
}
public void setGuide(Guide guide) {
this.guide = guide;
}
}
So the #ManyToOne annotation on the guide field:
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name="guide_id")
private Guide guide;
means that a single guide is associated a single student but a guide can follow many students. Is it right? What exactly does the specified cascade settings do? I think it means that when I persist a Student object that contains a Guide object as field also this Guide object is also automatically persisted. And the same thing happens when I remove a Student object, the related Guide record is deleted...but I am not absolutely sure about it...
Ok, doing it this way I will have a mono directional relationship between a record in the Student table and a record in the Guide table because in the Student table I will have a foreign key to join the Guide table so the student can know its guide but, doing it this way, the guide can not know the followed student...and this is not smart.
To do it the Guide class is implemented in this way:
#Entity
public class Guide {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="staff_id", nullable=false)
private String staffId;
private String name;
private Integer salary;
#OneToMany(mappedBy="guide", cascade={CascadeType.PERSIST})
private Set<Student> students = new HashSet<Student>();
public Guide() {}
public Guide(String staffId, String name, Integer salary) {
this.staffId = staffId;
this.name = name;
this.salary = salary;
}
public Set<Student> getStudents() {
return students;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
public void addStudent(Student student) {
students.add(student);
student.setGuide(this);
}
}
So, as you can see, this class contains:
#OneToMany(mappedBy="guide", cascade={CascadeType.PERSIST})
private Set<Student> students = new HashSet<Student>();
that it is used to declare the bidirectional relationship.
So it seems to me that this annotation automatically create a guide_id field into the Student table that represent the foreign key that implement the bidirectional relation.
In fact using this mapping the Student table is automatically created in this way in my database:
'id', 'bigint(20)', 'NO', 'PRI', NULL, 'auto_increment'
'enrollment_id', 'varchar(255)', 'NO', '', NULL, ''
'name', 'varchar(255)', 'YES', '', NULL, ''
'guide_id', 'bigint(20)', 'YES', 'MUL', NULL, ''
So in the Student entity class I have not defined the guide_id field but I have it in the Student table on the database. So I think that the creation of this field in the table depends on the previous #OneToMany annotation defined in the Guide entity class. Is that correct or am I missing something?
Yes, you can define a #OneToMany entity without a bidirectional association, and the added column is on the Many entity side in the database (even though the entity doesn't know it is linked to the One-side entity).
You can also use a join table for this, but it's not necessary.

PersistenceException, Column 'id' specified twice

I have the following files in Play Framework 2.2.3
Controller:
public class Comment extends Controller
{
public Result create(UUID id)
{
models.blog.Blog blog = models.blog.Blog.finder.byId(id);
Result result;
if(blog == null)
{
result = notFound(main.render("404", error404.render()));
}
else
{
Form<models.blog.Comment> commentForm = Form.form(models.blog.Comment.class);
commentForm = commentForm.bindFromRequest();
if(commentForm.hasErrors())
{
result = badRequest(Json.toJson(commentForm));
}
else
{
models.blog.Comment comment = commentForm.get();
comment.setId(UUID.randomUUID());
comment.setTimeCreated(new Date());
comment.setBlogId(blog.getId());
comment.save();
result = ok(Json.toJson(comment));
}
}
return result;
}
}
And two models
#Entity
#Table(name="blog")
public class Blog extends Model
{
private static final SimpleDateFormat MONTH_LITERAL = new SimpleDateFormat("MMMMM"),
DAY_NUMBER = new SimpleDateFormat("d"),
YEAR_NUMBER = new SimpleDateFormat("yyyy");
public static Finder<UUID, Blog> finder = new Finder<UUID, Blog>(UUID.class, Blog.class);
#Id
#Column(name="id",length=36, nullable=false)
public UUID id;
#OneToOne
#JoinColumn(name="author_id")
public User author;
#Column(name="title",length=255)
public String title;
#Column(name="summary",length=255)
public String summary;
#Column(name="url",length=255)
public String url;
#Column(name="content")
public String content;
#Column(name="time_updated")
public Date time_created;
#Column(name="time_created", nullable=false)
public Date time_updated;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="blog_id")
public List<Comment> comments;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name="blog_tag_map",
joinColumns={ #JoinColumn(name="blog_id", referencedColumnName="id") },
inverseJoinColumns={ #JoinColumn(name="tag_id", referencedColumnName="id") }
)
public List<Tag> tags;
public List<Comment> getComments()
{
return this.comments;
}
}
#Entity
#Table(name="blog_comment")
public class Comment extends Model
{
private static final SimpleDateFormat MONTH_LITERAL = new SimpleDateFormat("MMMMM"),
DAY_NUMBER = new SimpleDateFormat("d"),
YEAR_NUMBER = new SimpleDateFormat("yyyy");
#Id
#Column(name="id",length=36, nullable=false)
public UUID id;
#Column(name="blog_id", length=36)
public UUID blog_id;
#ManyToOne
public Blog blog;
#Column(name="content", length=500)
public String content;
#Column(name="website", length=255)
public String website;
#Column(name="name", length=255)
public String name;
#Column(name="time_created", updatable=false)
public Date time_created;
}
I have excluded some setters and getters from these models for brevity, so it doesn't clog up this post.
When I attempt to make a POST request to the aforementioned controller, everything goes fine until I get to the "comment.save()" statement in the controller file, then I get the following error.
I'm unsure why this save isn't going through, and why there is a column conflict.
Help much appreciated
The issue lies in the fact that you have defined basically two foreign key columns for Blog in your Comment's entity:
#Column(name = "blog_id", length = 36)
public UUID blog_id;
#ManyToOne
public Blog blog;
The default column name for your 'blog' field is: blog_id
However, you've already named your 'blog_id' column that.
Interestingly, no error/warning is thrown when creating this table...
So when you call comment.save(), the following insert statement is generated:
insert into blog_comment (id, blog_id, content, website, name, time_created, blog_id) values (?,?,?,?,?,?,?)
Notice a reference to 'blog_id' column twice, which is invalid.
And this is because of the above double mapping.
To fix, just give your 'blog' property a different name to use for the foreign key column:
#Column(name = "blog_id", length = 36)
public UUID blog_id;
#ManyToOne
#JoinColumn(name = "blogId")
public Blog blog;
I'm not sure why you're mapping your entities like this (perhaps legacy schema?) but the 'blog_id' fields seem to be redundant (and confusing) as you already have an entity mapping in the form of your 'blog' property.
This question is pretty old, but for any future reference i have found this answer that solved my problem.
After numerous searchers around the web I found this answer here - thanks to jtal!
Just to summaries the problem:
Using Ebean i have made a #ManyToOne entity that is not implemented in the database in anyway,
even more the join field, in your case
blogId
is a valid field that has values of its own.
when trying to join the column on that field, it will always fail because it creates this sql query:
SELECT
*
FROM
blog_comment;
select
t0.id c0,
t0.blog_id c1,
t0.content c2,
t0.website c3,
t0.time_created c4,
t0.blog_id c5 <---- notice this duplicate
from
blog_comment t0
in order to solve this, i tell ebean not to use the second set of properties.
your new ebean element should look something like this:
#ManyToOne
#JoinColumn(name = "blogId", insertable = false, updatable = false)
public Blog blog;
hope this helps! =)

Hibernate Search #IndexedEmbedded

I have a similar situation like this one
#Entity
#Indexed
public class Place {
#Id
#GeneratedValue
#DocumentId
private Long id;
#Field( index = Index.TOKENIZED )
private String name;
#OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )
#IndexedEmbedded
private Address address;
....
}
#Entity
public class Address {
#Id
#GeneratedValue
private Long id;
#Field(index=Index.TOKENIZED)
private String street;
#Field(index=Index.TOKENIZED)
private String city;
#ContainedIn
#OneToMany(mappedBy="address")
private Set<Place> places;
...
}
The problem now is the following:
If I change for example the name field in entity Place which entities are going to be re-indexed?
1) Only the name field?
2) The whole Place entity?
3) The whole Place entity and the entities annotated with #IndexedEmbedded?
The one I need for my purpose would be the third. So if it is not standard, could there be any solution to achieve this behaviour?
Fortunately it's really the third, so I was lucky and it works as expected
You can use the following code to re-index the Place Entity manually
public void updateIndex(T entity){
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
fullTextEntityManager.index(entity);
fullTextEntityManager.getSearchFactory().optimize(entity.getClass());
}
Secondly if you are using hibernate you can configure lucene in persistence.xml to automatically update the indexes of the entities that are changed

How to store some of the entity's values in another table using hibernate?

is there a simple way to persist some of the fields in another class and table using hibernate.
For example, I have a Person class with name, surname, email, address1, address2, city, country fields. I want my classes to be:
public class Person
{
private String name;
private String surname;
private String email;
private Address address;
// ..
}
public class Address
{
private Person person; // to whom this belongs
private String address1;
private String address2;
private String city;
private String country;
// ..
}
and I want to store Address in another table. What is the best way to achieve this?
Edit: I am using annotations. It does not have to be the way I described, I am looking for best practices.
Edit 2: What will be the Id of Address?
PS. If there is a way to make Address immutable (to use as a value object) that is even better, or maybe not because I thought everything from wrong perspective :)
map Address as an entity and add a primary key (an auto-generated id)
map the relation between Person and Address as one-to-one (#OneToOne on each field)
With Hibernate 3.5 it is possible to define foreign generators (aka. JPA mapping), details are here.
It is pretty straight forward Person should implement Serializable then #Id annotation is added to person.
#Entity
#AccessType(value = "field")
#Table(name = "addresses")
public class Address
{
#Id
#OneToOne
#JoinColumn(name = "person_id")
private Person person;
// ...
}
There is an alternative but I really like the first one:
#Entity
#AccessType(value = "field")
#Table(name = "addresses")
public class Address
{
#Id
private int personId;
#MapsId
#OneToOne
#JoinColumn(name = "person_id")
private Person person;
// ...
}

Categories

Resources