Java JPA - save() an object with #JoinTable attribute - java

I'm trying to insert an row into my database. I have following sql setup (its just an example):
Table person:
(id, name)
Table person_street:
(person_id, street_id)
Table street
(id, name)
This should be a many to many relation. This example doesn't make sense, but I think you'll understand the idea.
Now I have these entities in java
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#JoinTable(
name = "person_street",
joinColumns = #JoinColumn(name = "person_id"),
inverseJoinColumns = #JoinColumn(name = "street_id")
)
#ManyToMany
private List<Street> streets;
}
This is a good solution to work with my person objects (after reading them from my database).
But I have problems while inserting/creating person objects.
I want to create a new person in my frontend. It calls an REST interface at my backends side with String name, List<Long> streets.
Now I want to insert this new person (with the given name and streets) into my database.
But I don't want to do a select for all List<Long> streets on my street table. There is no need to change any value of the street objects. I just want to insert the link between the new person and the existing streets (in the table person_street).
What is the easiest way to do that?
Can I use my Person class for this, or does I need a new different class.
T

you can add this method to the Person class
public void addStreets(Street street) {
if(streets==null) {
streets=new ArrayList<Street>();
}
streets.add(street);
}
and after that, you get the street by the id from the street table and added to the corresponding person which you are getting from the front end after that you save the whole person.

Related

Hibernate and Criteria Api generates wrong Join condition

I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee

How to get a list of entity members which has manyToMany relationship with other entity mapped with JPA?

I have tables Authors and Books (with join table Authorsbooks) in database. I created and initiated tables with MySQL. I have also made (a standard) JPA mapping with two entities Authors and Books using #manyToMany and #Jointable in one entity and mappedBy in the other and connected it to database.
When I try to get all members of Authors entity with Java with method findAll() it returns an endless sequence: like a first member of authors with list of books, in which a first Book contain list of Authors where it contain first Book which contain list of authors and so on, endless. How to get only authors w/o field listOfBooks (like I have it in the database table)?
#Entity
public class Author {
#Id
#GeneratedValue
#Column(name = "authorid")
private Integer authorid;
#Column(name = "authorname")
private String authorname;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "listOfAuthors")
private List<Book> listOfBooks = new ArrayList<Book>();
//getters and setters
#Entity
public class Book {
#Id
#GeneratedValue
#Column(name = "bookid")
private Integer bookid;
#Column(name = "bookname")
private String bookname;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "author2books", joinColumns = #JoinColumn(name = "bookid"),
inverseJoinColumns = #JoinColumn(name = "authorid"))
private List<Author> listOfAuthors = new ArrayList<Author>();
//getters and setters
#RestController
#RequestMapping(value = "/rest/authors")
public class AuthorResource {
#Autowired
AuthorsRepository authorsRepository;
#GetMapping(value = "/all")
public List<Author> getAll() {
return authorsRepository.findAll();
}
}
CREATE TABLE author
(
authorid INT AUTO_INCREMENT PRIMARY KEY,
authorname VARCHAR(255) NOT NULL
);
CREATE TABLE Book
(
bookid INT AUTO_INCREMENT PRIMARY KEY,
bookname VARCHAR(255) NOT NULL
);
CREATE TABLE author2books
(
authorid INT,
bookid INT,
PRIMARY KEY (authorid, bookid),
FOREIGN KEY (authorid) REFERENCES Author (authorid),
FOREIGN KEY (bookid) REFERENCES Book (bookid)
);
-- Create Author values
INSERT INTO Author VALUES (authorid, 'Sven');
INSERT INTO Author VALUES (authorid, 'Peter');
-- Create Book values
INSERT INTO Book VALUES (bookid, 'Biology');
INSERT INTO Book VALUES (bookid, 'Chemistry');
-- Create author2books values
INSERT INTO author2books VALUES (1, 2);
Entities mapping/relationship
Database script
I was surprised that there was not easy to find a solution for this in my opinion standard situation. I found two ways to solve it.
The first is a bit indirect: Making a query returning entity without including ManytoMany list field like described here:Spring JPA selecting specific columns.
Second way is more direct but looks quite exotic: using JsonBackReference and JsonManagedReference annotations like here: keenformatics.blogspot.se/2013/08/
You can try to mark the relationship Book -> author / Author -> book Lazy loaded to avoid entering an infinite loop :)
#ManyToMany(fetch = LAZY)

Switching foreign key in hibernate is not working

TLDR;
I'm using spring boot and jpa.
I want to switch the foreign key of an object, in this case just switching the category of a vehicle.
But when i try to do that hibernate interprets it as if i'm trying to change the primary key of the category object instead of just switching the foreign key and I get this error
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I have an entity Category which i'm using only for categorizing vehicle entity object.
#Entity
public class Category {
#Id
private Long id;
private String name;
}
Here is the Vehicle class which needs to be categorized.
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="dish_seq")
private Long id;
private String name;
private Integer price;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.DETACH)
private Category category;
}
Lets say there's 3 categories,
'Sedan'
'Convertible'
'Hatchback'
If i have a car object,
Nissan-PT76, $30000, category: [id:1, name:Sedan]
When i try to change category manually to [id:2, name:Convertible] and persist it, i get
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I cannot switch from one existing object to another. I have tried to look this up in the internet but i couldn't find the right keywords to search for this kind of relationship in hibernate, or does it not allow this kind of relationship at all?
Add column reference to your Category field in the Vehicle class
#JoinColumn(name = "category_id", nullable = false)

JPA (#OneToMany) query without joining child table

I'm writing a service with JPA and Postgres db. I have a class named Student:
public class Student {
#id
private String id;
private String firstName;
private String lastName;
#OneToMany(targetEntity = Phone.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "student_id", referencedColumnName = "id")
private Set<Phone> phones;
// Setter and Getter
}
And a Phone class:
public class Phone {
#id
private String id;
private String number;
// Setter and Getter
}
Now there will be two tables in my db, following are columns in them:
student: id, first_name, last_name
phone: id, number, student_id (generated by #JoinColumn)
Now every time when I query the student, JPA will join the phone table into student table and the student result contains the phone info of this student. This is exactly what I want.
But now I met a problem. When I query the list of the students, the phone info is not useful in this case, only the id, firstName and lastName are necessary. But JPA does the same "join" operation for me too. I know this consumes lots of time. In such case how could I just return the info in the student table? Without joining the phone table?
I tried something in the repository like
#Query(SELECT s.id, s.firstName, s.lastName FROM student s)
public List<Student> findAllStudentWithoutPhone();
but it returns me the list of the values, but not converted into a Student object. How could I achieve this feature?
In the one-to-many mapping (refer below) you have set the fetch type to be lazy fetch = FetchType.LAZY hence hibernate won't fetch set of phones corresponding to the student until you access the set of phones via getter method, so no need to worry about it.
#OneToMany(targetEntity = Phone.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "student_id", referencedColumnName = "id")
private Set<Phone>;
However in case you need to verify whether things are working fine or not you can set the property show_sql to true and check the sql generated via hibernate.
fetch = FetchType.LAZY provides you possibility don't query Phone table until of the first call from the code. Please see: http://docs.oracle.com/javaee/7/api/javax/persistence/FetchType.html#LAZY
In case if you want to retrieve the list of the students without phones query should be:
#Query(SELECT * FROM student s where phones IS NULL)
For automatically converting results into Student object please don't use s.id, s.firstName, s.lastName in your query.
Answer given by #Sahil is absolutely correct but to add that.
#Cong
You do not need to add FetchType.LAZY as by default its already LAZY.
& as a note the Student class property for phone missing variable name.

Complex query for hibernate

I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.

Categories