Hibernate Foreign Key Syntax - java

I am trying to learn hibernate. I have a movie table with a foreign key to a genre table. Each movie is assigned to a single genre. Each genre may be assigned to many movies.
Here are the table definitions:
CREATE TABLE `movie` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(64) NOT NULL,
`genre_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_genre` (`genre_id`),
CONSTRAINT `fk_genre` FOREIGN KEY (`genre_id`) REFERENCES `genre` (`id`) ON UPDATE CASCADE,
)
CREATE TABLE IF NOT EXISTS `genre` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
)
For code I have
#Entity
public class Movie implements java.io.Serializable {
private static final long serialVersionUID = 1;
private Integer id;
private String title;
private Genre genre;
...
#ManyToOne
public Genre getGenre() {
return genre;
}
Also
#Entity
public class Genre implements java.io.Serializable {
private static final long serialVersionUID = 1;
private Integer id;
private String name;
#Id
#GeneratedValue
#Column(name = "id")
public Integer getId() {
return this.id;
}
Then the select generated by hibernate looks like
select
movie0_.id as column1_4_,
movie0_.genre_id as genre11_4_,
movie0_.title as title4_
from
Movie movie0_
And this not right as there's no reference to the genre table. The correct query should have a join with the genre table. More like
select
movie0_.id as column1_4_,
genre.name as genre11_4_,
movie0_.title as title4_
from
Movie movie0_, Genre genre
where
movie0_.genre_id = genre.id;
I'm a little bit of a loss as to what I'm doing wrong. Should the many to one annotation be in the Genre class instead of the Movie class? Or do you see anything else that I'm doing wrong?
Based on the advise below, Movie now has
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(id).append(" ");
sb.append(title).append(" ");
this.getGenre(); //new
sb.append(genre.getName());
return sb.toString();
}
#ManyToOne(fetch=FetchType.EAGER) //new
public Genre getGenre() {
return genre;
}
And the way I'm loading Movie is through
public static void main(String[] args) {
SessionFactory sf = HibernateUtil.getSessionFactory();
Session session = sf.openSession();
List<Movie> movies = session.createQuery("from Movie").list();
for (Movie movie : movies) {
System.out.println(movie.toString());
}
session.close();
}
What I'm seeing is that even though I have the eager load and I'm explicitly saying getGenre in toString, no query is generated and I'm just getting a null back.

When you use HQL syntax (e.g. createQuery("from Movie")), then Hibernate/JPA will only fetch the Genre entity when you call getGenre() on your Movie object. This is called "lazy fetching". When the method is called, Hibernate will issue another query to fetch the Genre.
Note that HQL queries ignore the FetchType on your annotations - HQL is used to tell Hibernate exactly what to do, rather than using the hints in the annotations.
To make it fetch the Genre in the same query as the Movie, you need to tell it to:
createQuery("from Movie m join fetch m.genre")

try this:
Moive side:
#ManyToOne
#JoinColumn(name = "genre_id")
public Genre getGenre() {..}
and on the other side (genre):
#OneToMany(mappedBy="genre")
List/Set getMovies(){..}
then you can from a movie object movie.getGenre() get Genre.

Related

Cannot add or update a child row: a foreign key constraint fails MySQL Hibernate

It seems to be a common error. I've seen other topics, but nothing changed. Here is MySQL script:
DROP DATABASE IF EXISTS `library`;
CREATE DATABASE IF NOT EXISTS `library`;
USE `library`;
SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT;
SET #OLD_CHARACTER_SET_RESULTS=##CHARACTER_SET_RESULTS;
SET #OLD_COLLATION_CONNECTION=##COLLATION_CONNECTION;
SET NAMES utf8;
SET #OLD_UNIQUE_CHECKS=##UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO';
SET #OLD_SQL_NOTES=##SQL_NOTES, SQL_NOTES=0;
SET FOREIGN_KEY_CHECKS = 0;
CREATE TABLE customer
(
c_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
first_name CHAR(50) NOT NULL,
last_name CHAR(50) NOT NULL,
email CHAR(50) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
INSERT INTO customer VALUES
(1,'David','Bush','bushd#gg.com'),
(2,'John','Doe','johnd#gg.com'),
(3,'Max','Rao','maxr#gg.com'),
(4,'Mary','James','maryj#gg.com'),
(5,'Tony','Lord','tony#gg.com');
CREATE TABLE rental
(
rental_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
issue_date DATE,
return_date DATE,
customer_id INT,
book_id INT,
KEY `FK_CUSTOMER_idx` (`customer_id`),
CONSTRAINT `FK_CUSTOMER`
FOREIGN KEY (`customer_id`)
REFERENCES `customer` (`c_id`),
KEY `FK_BOOK_idx` (`book_id`),
CONSTRAINT `FK_BOOK`
FOREIGN KEY (`book_id`)
REFERENCES `book` (`b_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
INSERT INTO rental VALUES
(1,'2014-10-11',null,3,1),
(2,'2014-10-11',null,5,2);
CREATE TABLE book
(
b_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
author CHAR(50) NOT NULL,
title CHAR(100) NOT NULL,
category CHAR(50) NOT NULL,
availability CHAR(5) NOT NULL DEFAULT 'yes'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
INSERT INTO book VALUES
(1,'Doyle','The adventures of binkle and flip','Child','no'),
(2,'Blyton','The adventures of sherlock holmes','Crime fiction','no'),
(3,'Rustard','Insects','Science','yes'),
(4,'Larsson','Man who hate woman','Crime','yes'),
(5,'Tolkien','Lord of the Rings: The Two Towers','Fantasy','lost');
SET SQL_MODE=#OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=#OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=#OLD_UNIQUE_CHECKS;
SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT;
SET CHARACTER_SET_RESULTS=#OLD_CHARACTER_SET_RESULTS;
SET COLLATION_CONNECTION=#OLD_COLLATION_CONNECTION;
SET SQL_NOTES=#OLD_SQL_NOTES;
SET FOREIGN_KEY_CHECKS=1;
Here is the error:
ERROR: Cannot add or update a child row: a foreign key constraint fails
(`library`.`rental`, CONSTRAINT `FK_BOOK` FOREIGN KEY (`book_id`) REFERENCES
`book` (`b_id`))
Rental entity:
#Entity
#Table(name="rental")
public class Rental {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="rental_id")
private int id;
#Column(name="issue_date")
private Date issueDate;
#Column(name="return_date")
private Date returnDate;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name="book_id", referencedColumnName="b_id")
private Book book;
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name="customer_id", referencedColumnName="c_id")
private Customer customer;
Customer entity:
#Entity
#Table(name="customer")
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="c_id")
private int id;
#NotNull
#Column(name="first_name")
private String firstName;
#NotNull
#Column(name="last_name")
private String lastName;
#Email
#Column(name="email")
private String email;
#OneToMany(mappedBy="customer")
private List<Rental> rentals;
Book entity:
#Entity
#Table(name="book")
public class Book {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="b_id")
private int id;
#NotNull
#Column(name="author")
private String author;
#NotNull
#Column(name="title")
private String title;
#NotNull
#Column(name="category")
private String category;
#NotNull
#NotEmpty
#Column(name="availability")
private String availability;
#OneToMany(mappedBy="book")
private List<Rental> rentals;
Controller(values hardcoded for testing):
#PostMapping("/confirmRent")
public String confirmRent(/*PathVariable ("customerId") int cId,
#PathVariable ("bookId") int bId*/) {
int cId = 5;
int bId = 5;
Customer theCustomer = customerService.getCustomer(cId);
Book theBook = bookService.getBook(bId);
rentalService.createNewEntry(theCustomer, theBook);
return "redirect:/book/allAvailableBooks";
}
DAO :
#Override
public void createNewEntry(Customer theCustomer, Book theBook) {
Session currentSession = sessionFactory.getCurrentSession();
Rental rental = new Rental();
rental.setBook(theBook);
rental.setCustomer(theCustomer);
currentSession.save(rental);
}
Am I missing something out? PS: The code may look amateurish: I'm a beginner ;)

JPA Entitties, how to join tables

I have three tables
CREATE TABLE "ingredient" (
"id" INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY,
"ingredient" VARCHAR(50) NOT NULL
);
CREATE TABLE "pizza" (
"id" INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY,
"pizza" VARCHAR(50) NOT NULL
);
CREATE TABLE "pizza_structure" (
"pizza_id" INT NOT NULL,
"ingredient_id" INT NOT NULL,
"amount" INT NOT NULL
);
how to join them, to get Pizzas structure as a Map
#Entity
#Table(name = "ingredient")
public class Ingredient{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Ingredient() {
}
}
#Entity
#Table(name = "pizza")
public class Pizza {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany ????
private Map<Ingredient, Integer> pizzaStructure;
public Pizza() {
}
public Pizza(String name, Map<Long, Integer> pizzaStructure) {
this.name = name;
this.pizzaStructure = pizzaStructure;
}
}
do I need to create #Embeddable class PizzaStructure, if yes when how to use it?
now I'm getting an error
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class:
how to join them, to get Pizzas structure as a Map
It seems to look like this:
#ElementCollection
#CollectionTable(name = "pizza_structure", joinColumns = {#JoinColumn(name = "pizza_id")})
#Column(name = "amount")
#MapKeyJoinColumn(name = "ingredient_id")
private Map<Ingredient, Integer> pizzaStructure;
do I need to create #Embeddable class PizzaStructure
No.
More info is here: Hibernate User Guide - Maps.
Note that table pizza_structure should have foreign keys to pizza and ingredient tables and also unique constrain of pizza_id and ingredient_id, like this (it's postgresql dialect):
create table pizza_structure
(
pizza_id ... constraint fk_structure_pizza references pizza,
ingredient_id ... constraint fk_structure_ingredient references ingredient,
amount ...,
constraint pizza_structure_pkey primary key (pizza_id, ingredient_id)
);
You have a manyToMany relationship between pizza and ingredient and an additional column in your relationship.
I found a similar question here: JPA 2.0 many-to-many with extra column
(I would comment, but i do not have enough reputation.)

Hibernate (Spring JPA) child entities removal

I'm learning Hibernate (Spring) and facing strange issue with removing child entities from the parent one.
Here is what I have:
Parent entity:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "company_id", referencedColumnName = "id")
List<CompanyObject> companyObjects;
}
Child entity:
#Entity
public class CompanyObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Enumerated(EnumType.STRING)
ObjectType type;
#ManyToOne
#JoinColumn(name = "company_id")
Company company;
}
Here is my table definitions:
CREATE TABLE `company` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8
CREATE TABLE `company_object` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`type` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK__company` (`company_id`),
CONSTRAINT `FK__company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
And, also, I have the following update method:
// some code here
public void update(CompanyDto dto) {
Company company = repository.getCompanyById(companyId);
repository.save(dto.merge(company));
}
// some code here
public class CompanyDto {
private List<CompanyObjectDto> companyObjects = new ArrayList<>();
public Company merge(Company company) {
company.getCompanyObjects().clear();
for (CompanyObjectDto dto : companyObjects) {
company.getCompanyObjects().add(dto.to(company));
}
return company;
}
}
public class CompanyObjectDto {
ObjectType type;
public CompanyObject to(Company company) {
CompanyObject object = new CompanyObject();
object.setType(this.getType());
object.setCompany(company);
return object;
}
}
And as soon as I launch update method, I get the following error: java.sql.SQLWarning: Column 'company_id' cannot be null. I investigated this a little bit and found out that if I comment out company.getCompanyObjects().clear(); string it works ok, so it seems there is some problem with cascading delete action to company objects.
Could, please, somebody point me to my mistakes? Thanks.
You have mapped your entities Company and CompanyObject bidirectionally, i.e. both entities have a relation to the other entity.
In this case, there should only be one #Joincolumn and one entity must be selected as the owning entity, with the other entity marking the relation with a 'mappedby' (see http://docs.oracle.com/javaee/6/api/javax/persistence/ManyToOne.html).
You are getting error because you are removing object's from List and then use the same List as a reference to your Company object. See below code :
private List<CompanyObjectDto> companyObjects = new ArrayList<>(); //Stmt 1
Above code is used to define list which will be reference in your below code :
company.getCompanyObjects().clear(); //It will clear out all objects
for (CompanyObjectDto dto : companyObjects) { //Iterating over empty list defined in stmt 1.
company.getCompanyObjects().add(dto.to(company));
}
So your foreign key will always be null which is not permitted and throws exception.
And your code works when you comment out List#clear line because in that scenario, list already have some referenced objects which didn't modify.

Persisting java entities in database

I don't have yet any knowledge relating ORM, Hibernate, JPA and such, so is this a valid way of mapping associations between entities in database, without using them ?
Thanks.
User can register a company.
Each company has many departments.
Each department has many sectors, and so on with offices, employees etc.
public class Company{
private String company_name;
private int company_registration_number;
private String company_address;
public Company(String company_name, int crn, String company_address) {
this.company_name = company_name;
this.company_registration_number = crn;
this.company_address = company_address;
}
/* Getters, Setters, toString */
}
Department class
public class Department {
private String dept_name;
private String dept_description;
public Department( String dept_name, String dept_description) {
this.dept_name = dept_name;
this.dept_description=dept_description;
}
/*Getters, Setters, toString*/
}
CompanyDB
public class CompanyDB {
public boolean createCompany(Company company) throws SQLException {
String sql_query = "INSERT INTO " + DB_NAME + ".company VALUES (?, ?, ? )";
}
//other crud methods
}
DepartmentDB
public class DepartmentDB {
public boolean createDepartment(Department department, String company_name) throws SQLException {
String sql_query = "INSERT INTO " + DB_NAME +
".department(dept_name, dept_description, company_name)" +
" VALUES(?, ?, ?) " +
" WHERE company_name=?";
}
//other crud methods
}
SQL:
create table `company`(
`company_name` varchar(250),
`company_registration_number` int not null,
`company_address` varchar(250) not null,
primary key(`company_name`)
);
create table `department` (
`dept_id` int auto_increment,
`dept_name` varchar(250) not null unique,
`dept_description` varchar(512) not null,
`company_name` varchar(250),
primary key(`dept_id`) ,
foreign key(`company_name`) references `company`(`company_name`)
);
You may want to check out Hibernate relations or annotations as this could make your life a lot easier.
See
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/associations.html
or https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/ for some basics.
It seems like you've got the basics down but Hibernate/JPA provides the functionality such that you only have to create the database tables and relations or the entities with mapping and it will automatically generate the other half for you.
An example of what you are trying to do
public class Company {
#Id
private Long id;
#Column(name = "company_name")
private String companyName;
#Column(name = "company_registration_number")
private int companyRegistrationNumber;
#Column(name = "company_address")
private String companyAddress;
#OneToMany
private Set<Department> departments;
/* Getters, Setters, toString */
}
and
public class Department {
#Id
private Long id;
#Column(name = "dept_name")
private String deptName;
#Column(name = "dept_description")
private String deptDescription;
#ManyToOne
private Company company;
/*Getters, Setters, toString*/
}
In this case Hibernate will automatically associate the Company with all of the Departments by creating a column on the Department table (on the primary key for the company) so that it can associate all departments with said Company.
For JPA entities mapping is accomplished by annotations, also tables may be generated by Hibernate plugin please read about this. This link may help http://docs.jboss.org/tools/latest/en/hibernatetools/html/plugins.html

mysql set datatype column to java set mapping

I having issues in mapping a mysql SET type to Java Set using JPA
To illustrate my question i frame a random example below
Here is a table which has a column genre which is of type Set (i.e:it will be a collection of Strings)
CREATE TABLE `MusicCD` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`period` ENUM('Classical', 'Modern','Antique') NOT NULL,
`genre` SET('horror','thriller','comedy','drama','romance') ,
PRIMARY KEY (`id`)
)
Below is the entity class used for the mapping
#Entity
#Table(name = "MusicCD")
class MusicCD {
private long id;
private Period period;
private Set<String> genre;
//other getter setters //
#Column(name = "genre")
#ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
public Set<String> getGenre() {
return genre;
}
public void setGenre(Set<String> genre) {
this.genre = genre;
}
}
With this mapping there is no exception but the set is empty in the entity object because the get query sent by JPA/hibernate sents query for all fields in table MusicCD but for the genre it sends a separate query to table MusicCD_genre
When i see the sql schema there is a autogenerated table MusicCD_genre which is empty.
Sending a sql select query for genre on MusicCD returns the genres.
So how does the Set data type in sql work and what is the correct annotation to map it?
Update:
I also tried
#TypeDefs({#TypeDef(name = "javaSet", typeClass = HashSet.class)})
and annotate the getter with
#Type(type = "javaSet")
but this doesn't work with EOFException during de-serialization.
This might work by replacing the HashSet with correct type to deserialize to.
I know it's an old question, but I prefer treat these ´MySQL special type´ columns in the getters/setters when the most use of them would be in java code.
#Entity
#Table(name = "MusicCD")
class MusicCD {
/*...*/
#Column(name = "genre")
private String genreStr;
/*...*/
public Set<String> getGenre() {
if(genreStr == null)
return Collections.emptySet();
else
return Collections.unmodifiableSet(
new HashSet<String>(Arrays.asList(genreStr.split(",")))
);
}
public void setGenre(Set<String> genre) {
if(genre == null)
genreStr = null;
else
genreStr = String.join(",", genre);
}
}
I use the immutable version of Set, because that avoids trying alter the set values without actually alter the DB.

Categories