#PrimaryKeyJoinColumn: Joining tables by primary key not working - java

I have below two tables:
`message` (
`id` varchar(150),
`Message` varchar(1000) ,
PRIMARY KEY (`id`)
)
fallback_status (
message_key varchar(150),
fallback_delivere` tinyint(1),
fallback_read tinyint(1),
fallback_answered tinyint(1),
PRIMARY KEY (message_key)
)
I don't have any foreign key relationship at database level. I have created two entity for these tables
#Entity
#Table(name = "message")
public class Message {
#Id
#Column(name = "id", unique = true)
private String key;
#Column(name = "message")
String message
#OneToOne
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "message_key")
private MessageFallbackStatus messageFallbackStatus;
}
and
#Entity
#Table(name = "fallback_status")
public class MessageFallbackStatus {
#Id
#Column(name="message_key")
private String messageKey;
#Column(name="fallback_delivered")
private boolean fallbackDelivered;
#Column(name="fallback_read")
private boolean fallbackRead;
#Column(name="fallback_answered")
private boolean fallbackAnswered;
}
With this configuration Hibernate not able to persist data in fallback_status table.
my questions are:
Is the foreign key relationship is mandatory at database level?
what would be the proper mapping for this scenario?
thanks for any help.

Related

JPA mappedBy reference an unknown target entity

I am writing a simple inventory database that contains tables for products, orders and customers. The database definition can be found here:
CREATE TABLE public.customers
(
id integer NOT NULL DEFAULT nextval('customers_id_seq'::regclass),
title character varying(10) COLLATE pg_catalog."default" NOT NULL,
first_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
middle_names character varying(50) COLLATE pg_catalog."default",
last_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
email character varying(50) COLLATE pg_catalog."default" NOT NULL,
phone_number character varying(50) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT customers_pkey PRIMARY KEY (id)
)
CREATE TABLE public.products
(
id integer NOT NULL DEFAULT nextval('products_id_seq'::regclass),
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
sku integer NOT NULL,
inventory_on_hand integer NOT NULL,
reorder_threshold integer NOT NULL,
price numeric(5,2),
inventory_to_be_shipped integer NOT NULL,
CONSTRAINT products_pkey PRIMARY KEY (id)
)
CREATE TABLE public.order_items
(
id integer NOT NULL DEFAULT nextval('order_items_id_seq'::regclass),
product_id integer NOT NULL,
order_id integer NOT NULL,
CONSTRAINT order_items_pkey PRIMARY KEY (id),
CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id)
REFERENCES public.orders (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id)
REFERENCES public.products (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
CREATE TABLE public.orders
(
id integer NOT NULL DEFAULT nextval('orders_id_seq'::regclass),
customer_id integer,
order_date date NOT NULL DEFAULT now(),
arrival_date date,
CONSTRAINT orders_pkey PRIMARY KEY (id),
CONSTRAINT orders_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES public.customers (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
I am trying to implement a Spring Security Resource server to perform CRUD operations on the database. I have implemented entity classes for each table in the database but when try to start the server I get a
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: edu.finalyearproject.imsresourceserver.models.Order.customers in edu.finalyearproject.imsresourceserver.models.Customer.orders
My entity and repository classes can be found below:
Product.java:
#Entity
#Table(name = "products")
#Data
public class Product
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer sku;
private Float price;
private Integer inventory_on_hand;
private Integer reorder_threshold;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
Customer.java
#Entity
#Table(name = "customers")
#Data
public class Customer
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String title;
private String first_name;
private String middle_names;
private String last_name;
private String email;
private String phone_number;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
Order.java
#Entity
#Table(name = "orders")
#Data
public class Order
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name="customer_id", nullable=false)
private Customer customer;
private Date order_date;
private Date arrival_date;
#ManyToMany(mappedBy = "orders", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Product> products = new HashSet<>();
}
I know the problem is related to the relationships between the entities, but I haven't been able to find a solution. Any help would be greatly appreciated.
Try to correct this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "orders", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
to this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
See additional explanation in the documentation.
And you should correct also your Product-Order #ManyToMany association. Only one side of this association should use #JoinTable other side should use mappedBy property of the #ManyToMany annotation. Something like this:
#Entity
public class Product
{
// ...
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY
)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
#Entity
public class Order
{
// ...
#ManyToMany(
mappedBy = "orders",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<>();
}
As it is stated in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.
Also as this is explained in this section of the documentation:
If you forget to JOIN FETCH all EAGER associations, Hibernate is going to issue a secondary select for each and every one of those which, in turn, can lead to N+1 query issues.
For this reason, you should prefer LAZY associations.

Hibernate ERROR: Cannot delete or update a parent row: a foreign key constraint fails

I have the following classes:
#Entity
#Table(name = "lecture")
public class Lecture {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String name;
#OneToOne
#JoinColumn(nullable = false, name = "professorId")
#JsonIgnore
private Professor professor;
}
And:
#Entity
#IdClass(ListenTo.class)
#Table(name = "listen_to")
public class ListenTo implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#JoinColumn(nullable = false, name = "lectureId")
private Lecture lecture;
#Id
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(nullable = false, name = "studentId")
private Student student;
}
And I want to delete a lecture through this function:
public void delete(Lecture lecture) {
currentSession().delete(lecture);
}
I created the table like this:
create table lecture (
id bigint primary key not null auto_increment,
name varchar(500) not null,
professorId bigint not null,
foreign key (professorId) references professorId (id)
);
create table listen_to (
lectureId BIGINT not null references lecture(id),
studentId BIGINT not null references student(id),
primary key(lectureId,studentId)
);
However, I keep getting this error:
Causing: java.sql.SQLIntegrityConstraintViolationException: (conn=10) Cannot delete or update a parent row: a foreign key constraint fails (`myDBS`.`listen_to`, CONSTRAINT `listen_to_ibfk_1` FOREIGN KEY (`lectureId`) REFERENCES `lecture` (`id`))
I tried multiple things, including using this function to delete:
public boolean deleteById(Class<?> type, Serializable id) {
Object persistentInstance = currentSession().load(type, id);
if (persistentInstance != null) {
currentSession().delete(persistentInstance);
return true;
}
return false;
}
but it still doesn't work..
You first have to delete all ListenTo entries that refer to this lecture before you can delete the lecture. Use delete from ListenTo l where l.lecture.id = :id and bind the lecture id, before you delete the lecture itself.

Hibernate extra queries, issue with #GeneratedValue

Problem with Hibernate and associations (mapping) for existing child values and the #GeneratedValue strategies. Additional queries are needed?
I am learning Hibernate, and practicing with my own example using one-to-many, many-to-one and many-to-many associations.
Simplifying for the sake of the query:
I have Java classes User, Country and Languages. One User can only live in one Country (so relation is many-to-one) and can speak several Languages (so relation is many-to-many)
I have database tables 'users', 'countries', 'languages' and 'users_languages'. The 'country_id' is stored in the 'users' table (because there is only one per user), and the several 'language_id' and 'user_id' are stored in the 'users_languages' table.
I have prefilled the tables 'countries' and 'languages', as these are fixed values, so that I do not want to add/remove any of these values from these tables. They will be associated to the different users.
The schema of the tables (I removed columns for brevity):
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`country_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username_UNIQUE` (`username`),
CONSTRAINT `u_country_fk` FOREIGN KEY (`country_id`) REFERENCES `countries` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
)
CREATE TABLE `countries` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
)
CREATE TABLE `languages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`language` varchar(15) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `language_UNIQUE` (`language`)
)
CREATE TABLE `users_languages` (
`user_id` int(11) NOT NULL,
`language_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`language_id`),
CONSTRAINT `ul_language_fk` FOREIGN KEY (`language_id`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `ul_user_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
)
As commented, I already inserted the needed rows in tables 'countries' and 'languages' so that those will be the only available for the users.
The corresponding Java entities:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#NaturalId
#Column(name = "username", nullable = false, unique = true)
private String username;
#ManyToOne
#JoinColumn(name="country_id",
foreignKey = #ForeignKey(name = "u_country_fk"))
private Country country;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(
name = "users_languages",
joinColumns = #JoinColumn(name = "user_id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "language_id", nullable = false)
)
private List<Language> languages;
public User() {
this.languages = new ArrayList<>();
}
public User(String username, Country country) {
this.username = username;
this.country = country;
this.languages = new ArrayList<>();
}
public void addLanguage(Language language) {
this.languages.add(language);
language.getUsers().add(this);
}
public void removeLanguage(Language language) {
this.languages.remove(language);
language.getUsers().remove(this);
}
public void removeAllLanguages() {
this.languages.clear();
}
// getters and setters omitted
}
#Entity
#Table(name = "countries")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#NaturalId
#Column(name = "name", nullable = false, unique = true)
private String name;
// #OneToMany(cascade = CascadeType.ALL)
// #JoinColumn(name="id")
// private Set<User> users;
public Country() {
// users = new HashSet<>();
}
public Country(String name) {
this.name = name;
// users = new HashSet<>();
}
// getters and setters omitted
}
#Entity
#Table(name = "languages")
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#NaturalId
#Column(name = "language", nullable = false, unique = true)
private String language;
#ManyToMany(mappedBy = "languages")
Set<User> users;
public Language() {
users = new HashSet<>();
}
public Language(String language) {
this.language = language;
users = new HashSet<>();
}
// getters and setters omitted
}
When creating a new user, I have had issues with the #GeneratedValue, because in the batch I have to store several languages. Finally the one which worked for me is GenerationType.IDENTITY, but I read about the extra query required by Hibernate to know the next id to use. Furthermore, because the batch if I create a new object Language (which already exists in the database), there is an issue (I expected that Hibernate could manage this automatically). The same for new object Country. Therefore, instead of creating new objects, as the corresponding rows already exist in the database, I am getting them from the database, to associate them to the user being created. But I think that Hibernate will do again the same queries (check the existence in the database of the objects Language and Country being associated to the user), so that probably I am duplicating the number of required queries, reducing the performance. See code snippet below:
public int addUser(SessionFactory factory, String username, String countryName, String[] languages) {
User user = null;
try (Session session = factory.openSession()) {
Transaction transaction = session.beginTransaction();
Country country = (Country) session
.createQuery("FROM Country WHERE name = :country_name")
.setParameter("country_name", countryName).getSingleResult();
user = new User(username, password, name, email, gender, country);
for (String language : languages) {
Language lang = (Language) session
.createQuery("FROM Language WHERE language = :language")
.setParameter("language", language).getSingleResult();
user.addLanguage(lang);
}
session.persist(user);
transaction.commit();
}
return user==null ? -1 : user.getId();
}
Can someone please help me with the below points:
Am I using the correct strategy for generating the ids?
Am I using the correct cascade methodology (both in database and Hibernate)?
Is there a better way for the above procedure for addigin a user, reducing the total number of queries to the database / increasing the performance?
Should I make the Country association biderectional as well? Recommendations about uniderectional/bidirectional for both Country and Language classes.
Any other suggestion / improvement I should apply with Hibernate?
Many thanks in advance,
Javier

Hibernate - Crash with 2 #OneToMany

Hibernate crashes when using more than one #OneToMany entries in my Users.entity and I don't understand why.
I have a table for users (primary key userID) and various other tables which refer to the primary key userID by a foreign key set in database (InnoDB set and foreign key is set in each depending table).
Here an example with three tables:
Table users:
CREATE TABLE `users` (`userID` int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `users` ADD PRIMARY KEY (`userID`);
Table: vacationrequests
CREATE TABLE `vacationrequests` (`requestID` int(11) NOT NULL,`userID` int(4) NOT NULL,) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `vacationrequests` ADD CONSTRAINT `userID` FOREIGN KEY (`userID`) REFERENCES `users` (`userID`) ON UPDATE CASCADE; COMMIT;
Table monthend:
CREATE TABLE `monthend` (`monthendID` int(11) NOT NULL,`userID` int(4) NOT NULL,) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Entity Users:
#Entity
#Table(name="users")
public class Users {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="userID")
private int userID;
... .... (other variables)
#OneToMany(fetch = FetchType.LAZY, mappedBy="monthend")
private Set<Monthend> monthend;
#OneToMany(fetch = FetchType.LAZY, mappedBy="vacationrequests")
private Set<Vacationrequests> vacationrequests;
public Users() {
}
Entity Vacationrequests:
#Entity
#Table(name="vacationrequests")
public class Vacationrequests {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="requestID")
private int requestID;
... .... (other variables)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userID", nullable = false)
private Users users;
public Vacationrequests() {
}
Entity Monthend:
#Entity
#Table(name="monthend")
public class Monthend {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="monthendID")
private int monthendID;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userID", nullable = false)
private Users users;
#Column(name="clientID")
private int clientID;
#Column(name="month")
private int month;
#Column(name="year")
private int year;
#Column(name="monthend")
private int monthend;
public Monthend() {
}
Working Query:
List<Object> listJoinUsersMonthend = session.createQuery("from Monthend m inner join m.users as users where m.clientID = :clientID AND m.year = :year AND users.loginclass = 0 AND users.isactive = 1 AND m.monthend = 0 AND m.month < :month order by users.userID asc")
.setInteger("clientID", user.getClientID())
.setInteger("year", year)
.setInteger("month", month)
.getResultList();
This second query i would like to integrate:
List<Object> listJoinVacationrequestsUsers = session.createQuery("from Vacationrequests v inner join v.users as users where v.clientID = :clientID AND v.approved=0 AND v.vacationtype=1 AND YEAR(v.startdate) = :year" )
.setInteger("clientID", user.getClientID())
.setInteger("year", year)
.getResultList();
Everything works fine with just one query and one entry in the Users.entity. As soon as I add the two entries hibernate just crashes and there is no error message. Is it not possible to do two #OneToMany statements in a entity ?
It seems that the problem is in the mapping of the associations in the Users entity.
You should change the mappedBy attribute of the associations to specify the field name that references the current entity in the associated one (in this case users), like this:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "users")
private Set<Monthend> monthend;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "users")
private Set<Vacationrequests> vacationrequests;

manyToOne and oneToMany in hibernate&spring

I have 2 tables in database side(oracle)
create table GROUPS
(
ID NUMBER not null,
GROUP_NAME VARCHAR2(30)
)alter table GROUPS
add constraint ID primary key (ID)
and
create table ITEM_GROUP
(
ITEM_ID VARCHAR2(30) not null,
GROUP_ID NUMBER not null
)
alter table ITEM_GROUP
add constraint ITEM_GROUPD_ID primary key (ITEM_ID, GROUP_ID)
alter table ITEM_GROUP
add constraint ITEM_GROUP_FK01 foreign key (GROUP_ID)
references GROUPS (ID);
Than I have mapping classes in Java side. I want to make thing, when I am selecting group to take all his items too, and I want to save item with hibernate it is all .
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#Column(name = "ID", nullable = false)
#javax.persistence.SequenceGenerator(name = "groupIdGenerator", sequenceName = "GROUP_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "groupIdGenerator")
private int id;
#Column(name = "GROUP_NAME")
private String groupName;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER, mappedBy="group",cascade = CascadeType.ALL)
private List<GroupItems> groupItems = new ArrayList<>();
// setters and getters
}
#SuppressWarnings("serial")
#Embeddable
public class GroupItemPK implements Serializable {
#Column(name = "ITEM_ID")
private String merchantId;
#Column(name = "GROUP_ID")
private int id;
// getters , setters , constructors , equals hashcode methods
}
#Entity
#Table(name = "ITEM_GROUP")
public class GroupITEM {
#EmbeddedId
private GroupITEMtPK id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID")
#JsonBackReference
private Group group;
}
I am interested in did i make any mistakes in build relationship ? If I did what is my mistakes , because I can not do my select and save queries without exceptions.
I am trying to do in my Code
List<Group> list = sessionFactory.getCurrentSession().createQuery("from Group a").list();
and here is my Exception
org.hibernate.engine.jdbc.spi.SqlExceptionHelper could not extract ResultSet [n/a]
java.sql.SQLSyntaxErrorException: ORA-00904: "GROUPITE0_"."ID": invalid identifier

Categories