Person.java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity
#Table(name="person")
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="name")
private String name;
#Column(name="age")
private int age;
#OneToOne(mappedBy="person")
private Passport passport;
----getter and setter----
Passport.java
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
#Entity
public class Passport {
#Id
private String passportNumber;
private Date issuedDate;
private Date expiryDate;
private String issuedPlace;
private String nationality;
#OneToOne
#Cascade(value={CascadeType.DELETE})
#JoinColumn(name="frn_person_id", unique=true, referencedColumnName="id")
private Person person;
---getter setter----
Hibernate Version: 4.2.0.Final
MySQL : 5.6.17
The above code generates SQL table like below:
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
CREATE TABLE `passport` (
`passportNumber` varchar(255) NOT NULL,
`expiryDate` datetime DEFAULT NULL,
`issuedDate` datetime DEFAULT NULL,
`issuedPlace` varchar(255) DEFAULT NULL,
`nationality` varchar(255) DEFAULT NULL,
`frn_person_id` int(11) DEFAULT NULL,
PRIMARY KEY (`passportNumber`),
KEY `FK4C60F032382EC083` (`frn_person_id`),
CONSTRAINT `FK4C60F032382EC083` FOREIGN KEY (`frn_person_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
However, I want to generate tables like below:
create table jaydb.person(
id integer auto_increment primary key,
name varchar(100) unique,
age integer
);
create table jaydb.passport(
passport_number varchar(50) primary key,
issued_date date,
expiry_date date,
issued_place varchar(100),
nationality varchar(100),
frn_person_id integer unique,
foreign key(p_id)
references jaydb.person(p_id)
on delete cascade
on update cascade
);
As you can see the cascade code is missing from the hibernate's generated tables. My requirement is to setup One-to-One mapping between Person and Passport. If person data gets deleted then it's associated passport should be deleted as well.
I've tried javax.persistence cascade as well but it didn't work.
I believe that in your schema, the Person class is the owning relationship, as evidenced by that you have the mappedBy property on the passport which the person owns. As far as I know, the cascade annotation should be on the owning side of the relationship. However, you currently have it on the passport. Change your Person class to look like this:
#Entity
#Table(name="person")
public class Person {
...
#OneToOne(mappedBy="person")
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
private Passport passport;
...
}
And remove the #Cascade annotatiom from the Passport class.
Related
I want to represent a one-to-one relationship with a driver and a license where the license is a weak entity having a composite key of the license number and the driver number.
The driver number is a foreign key that refers to the primary key of the user table.
CREATE TABLE user (
user_id SMALLINT NOT NULL GENERATED ALWAYS AS IDENTITY,
name VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL UNIQUE,
PRIMARY KEY (user_id)
);
CREATE TABLE license (
driver SMALLINT NOT NULL,
license_number SMALLINT NOT NULL GENERATED BY DEFAULT AS IDENTITY,
expiration_date timestamp NOT NULL,
PRIMARY KEY (driver, license_number),
FOREIGN KEY (driver) REFERENCES user(user_id)
);
How can I use the annotation #EmbeddedKey and #Embeddable?
My understanding is that there should be a new class called LicenseId that implements serializable and has two fields (the license number and the driver id), But I don't know how to tell JPA that this driver Id refers to the primary Id of the user
What I have done so far in terms of classes and entities:
import javax.persistence.Embeddable;
import java.io.Serializable;
#Embeddable
public class LicenseId implements Serializable {
private int driver;
private int licenseNumber;
}
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import java.sql.Date;
import java.sql.Timestamp;
#Entity
public class License {
#EmbeddedId
private LicenseId licenseId;
#Column(name = "expiration_date", nullable = false)
private Timestamp expirationDate;
}
I need a working example of the sample working Java web application for the MVC architecture. The technologies which are needed are only: JDBC with JSP and Servlet on, preferably MySQL. I don't need Hybernate and Spring. The sample DB may include only 2 tables like Users and UserRoles or (Products and ProductCategories, etc) which are joined on INNER JOIN.
Although a link to the sample project would be greatly appreciated, I'd also appreciate recommendations on what should I use:
UserRole class or enum for the sample project?
reference "private UserRole userRole;" or "private int userRoleId;" in User class?
as an example of the ListAllRecords method, how to utilize results of the query which joins users and user_roles tables:
SELECT users.*, user_roles.name FROM users INNER JOIN user_roles ON user_role_id = user_roles.id;
Here are details of my project which I'm trying to wrap my head around:
My design is:
Entity classes:
package com.project.entity.temp;
public class User {
private int id;
private String name;
private String password;
private UserRole userRole;
}
public class UserRole {
private int id;
private String name;
}
SQL queries:
-- -----------------------------------------------------
-- Schema mydb
-- -----------------------------------------------------
DROP SCHEMA IF EXISTS `mydb` ;
-- -----------------------------------------------------
-- Schema mydb
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 ;
USE `mydb` ;
-- -----------------------------------------------------
-- Table `mydb`.`user_roles`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`user_roles` ;
CREATE TABLE IF NOT EXISTS `mydb`.`user_roles` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `mydb`.`users`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`users` ;
CREATE TABLE IF NOT EXISTS `mydb`.`users` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`password` VARCHAR(45) NOT NULL,
`user_role_id` INT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
INDEX `fk_users_user_roles_idx` (`user_role_id` ASC) VISIBLE,
CONSTRAINT `fk_users_user_roles`
FOREIGN KEY (`user_role_id`)
REFERENCES `mydb`.`user_roles` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
I follow that approach, but was wondering whether it is the proper way to accomplish the goal:
Got result set using this query
SELECT users.*, user_roles.name FROM users INNER JOIN user_roles ON user_role_id = user_roles.id;
Looped through the records and populated UserRoles object from the ResultSet data
user = new User();
userRole = new UserRole();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
userRole.setId(resultSet.getInt("user_role_id"));
userRole.setName(resultSet.getString("user_roles.name"));
user.setUserRole(userRole);
When getting data from the JSP form will use the hidden fields.
Try below
Maven dependency -
javax.persistence
javax.persistence-api
2.2
Or
javax.persistence
persistence-api
1.0.2
package com.project.entity.temp;
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
#Entity
public class User {
#Id
private int id;
private String name;
private String password;
#OneToOne
private UserRole userRole;
}
#Entity
public class UserRole {
#Id
private int id;
#OneToOne
private User user;
private String name;
}
TL;DR
For OneToMany maping between Note and Tag using third table Note_tag, the save() is not able to save Note entity.
Background:
So I was workng on this NoteApp(Github repo location) which saves a Note with title, description, status and tag(tag being a String value). As a feature update, I thought to add multiple Tags for a Note. For this, I created a Tag table and a third association table of tag and note using faily straigt forward #JoinTable annotation. This feature led me to above mentioned issue while saving the Note entity.
What I am using behind the screen:
Java 1.8, Hiberanate, SpringBoot
More details on tech stack at here
What I already have working:
Save() Note without Tags.
My Note.java:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
#Entity
#Table(name = "T_NOTE")
public class Note implements Serializable {
private static final long serialVersionUID = -9196483832589749249L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private Integer id;
#Column(name="TITLE")
private String title;
#Column(name="DESCRIPTION")
private String description;
#Column(name = "LAST_UPDATED_DATE")
private Date lastUpdatedDate;
#Column(name="STATUS")
private String status;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "T_NOTE_TAG", joinColumns = { #JoinColumn(name="NOTE_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID") })
private List<Tag> tags = new ArrayList<Tag>();
/** getters setters omitted for brevity**/
}
My Tag.java:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "T_TAG")
public class Tag implements Serializable {
private static final long serialVersionUID = -2685158076345675196L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="TAG_ID")
private Integer tagId;
#Column(name="TAG_NAME")
private String tagName;
}
My NoteDto.java:
public class NoteDto {
private int id;
private String title;
private String description;
private List<TagDto> tags = new ArrayList<TagDto>();
private Date lastUpdatedDate;
private String status;
}
My TagDto.java:
public class TagDto {
private int tagId;
private String tagName;
}
My Table creation queries:
CREATE database if NOT EXISTS noteApp;
CREATE TABLE `T_NOTE` (
`id` int(11) unsigned NOT NULL auto_increment,
`description` varchar(20) NOT NULL DEFAULT '',
`header` varchar(20) NOT NULL,
`status` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `T_TAG` (
`tag_id` int unsigned not null auto_increment,
`tag_name` varchar(30) not null,
PRIMARY KEY(TAG_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `T_NOTE_TAG` (
`note_tag_id` int unsigned not null auto_increment,
`note_id` int unsigned not null,
`tag_id` int unsigned not null,
CONSTRAINT note_tag_note foreign key (`note_id`) references T_NOTE(`id`),
CONSTRAINT note_tag_tag foreign key (`tag_id`) references T_TAG(`tag_id`),
CONSTRAINT note_tag_unique UNIQUE (`tag_id`, `note_id`),
PRIMARY KEY (`note_tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
use noteApp;
ALTER TABLE T_NOTE
Change header title varchar(1000) NOT NULL;
ALTER TABLE T_NOTE
ADD COLUMN last_updated_date timestamp AFTER status;
Error logs:
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
test
Hibernate: insert into T_NOTE (DESCRIPTION, LAST_UPDATED_DATE, STATUS, TITLE, ID) values (?, ?, ?, ?, ?)
Hibernate: update T_TAG set TAG_NAME=? where TAG_ID=?
2020-04-11 18:02:40.647 ERROR 3614 --- [nio-8080-exec-1] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]]
org.springframework.orm.jpa.JpaOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]; nested exception is javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:396)
at org.springframework.orm.jpa.DefaultJpaDialect.translateExceptionIfPossible(DefaultJpaDialect.java:127)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:545)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
at org.springframework.transaction.interceptor.TransactionAspectSupport.
The exception suggests me that Tag is being updated wrongly which raises question that why is it calling update in first place for Tag whereas insert is correctly called for Note?
I tried finding solutions before posting this question but couldn't find any.
Thanks in advance!
Hibernate calls update for children on parent insertion because you used a uni-directional #OneToMany instead of #ManyToOne or a bi-directional. no matter what you do, Hibernate models this as a OneToMany. So, Hibernate inserts your parent and children, then calls update on children to add the parent foreign key to children's tables. Now on why you got the exception, as I said, Hibernate calls update on many side of your relationship and but since the default behavior is nullable set to true, it causes your error. Remember you are doing a uni-directional mapping so only one side of the relationship knows about it. Please avoid using uni-directional relationships of OneToMany. you can find many great articles about this matter on Vlad MihalCea website. He is a hibernate master. Set nullable to false and it should solve your problem.
Edit
Ensure that you update the tags before you save note. Also make sure that the service method saveNote in NoteServiceImpl is also annotated with #Transactional so that rollback will be initiated in case the query gets interrupted for some reason. I was able to reproduce the issue that you faced and guardian is right about how Hibernate handles such situations. You cannot save data to multiple tables in a single SQL command (this is related to Atomicity) but you achieve that using transactions. Here is a fantastic answer related to this.
In your code, you need to change saveNote method in NoteDaoImpl if you still want to save tags while saving data into T_NOTE.
public void saveNote(Note note) {
try {
Session session = this.sessionFactory.getCurrentSession();
for(Tag tag: note.getTags()){
session.saveOrUpdate(tag);
}
session.save(note);
} catch (Exception e) {
e.printStackTrace();
}
}
One thing that I would like to suggest would be to change the tags from List to Set to avoid repetition of data.
After making the above changes to the code, you should able to do successful POST and GET calls as shown below:
Update was being called due to mismatch of identifier datatype in my DTO and Entity(Note and Tag). As this was save() method, I was using int in NoteDTO and boxed type Integer in Note Entity. So, when I passed payload without id, input object resorted to default value 0. this 0 when converting to Note entity passed a 0 instead of null. So, for a child record of Tag, the id was 0 and thus hibernate might have resorted to update() seeing a non-null value for that child in her execution strategy.
I verified by coverting NoteDto id to Integer and this created correct queries. Below was my payload:
{
"title": "A Java SpringBoot note",
"description": "Desc for the note",
"lastUpdatedDate": "1486696408000",
"status": "New",
"tags": [
{
"tagName": "SpringBoot"
}
]
}
In TagDto:
public class TagDto {
private Integer tagId; ----> changed from int
private String tagName;
}
In NoteDto:
public class NoteDto {
private Integer id; ----> changed from int
private String title;
}
In NoteDaoImpl,
Session session = this.sessionFactory.getCurrentSession();
session.save(note);
Logs after success:
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
test
Hibernate: insert into T_NOTE (DESCRIPTION, LAST_UPDATED_DATE, STATUS, TITLE, ID) values (?, ?, ?, ?, ?)
Hibernate: insert into T_TAG (TAG_NAME, TAG_ID) values (?, ?)
Hibernate: insert into T_NOTE_TAG (NOTE_ID, TAG_ID) values (?, ?)
I ddnot mark #kavitha-karunakaran's as answer as saving Tag separately was not what I intend to do and that will defy the clean approach.
I didnt mark #guardian's answer as moving to a better approach was not my intention as of now but to correct my current approach of uni-directional mapping.
Thanks, to both of you for suggestions! Cheers!
In this scenario :
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.envers.Audited;
#Entity
#Audited
public class TestEntity implements Serializable {
#Id
private Long id;
#ElementCollection
private List<String> strings = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<String> getStrings() {
return strings;
}
public void setStrings(List<String> strings) {
this.strings = strings;
}
}
Hibernate creates two tables as expected:
CREATE TABLE testentity (
id bigint(20) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and
CREATE TABLE testentity_strings (
TestEntity_id bigint(20) NOT NULL,
strings varchar(255) DEFAULT NULL,
KEY FK6gvnp6uhj6p14qb8jr7w4a4sc (TestEntity_id),
CONSTRAINT FK6gvnp6uhj6p14qb8jr7w4a4sc FOREIGN KEY (TestEntity_id) REFERENCES testentity (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Envers also creates two tables :
CREATE TABLE testentity_aud (
id bigint(20) NOT NULL,
revision int(11) NOT NULL,
action tinyint(4) DEFAULT NULL,
PRIMARY KEY (id,revision),
KEY FKtoml4ns3581arnt5f7i1srxai (revision),
CONSTRAINT FKtoml4ns3581arnt5f7i1srxai FOREIGN KEY (revision) REFERENCES revinfo (REV)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and
CREATE TABLE testentity_strings_aud (
revision int(11) NOT NULL,
TestEntity_id bigint(20) NOT NULL,
strings varchar(255) NOT NULL,
action tinyint(4) DEFAULT NULL,
PRIMARY KEY (revision,TestEntity_id,strings),
CONSTRAINT FKadlc041c3dxra6fmfxsku0fuh FOREIGN KEY (revision) REFERENCES revinfo (REV)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The problem is with the second audit table (testentity_strings). It sets a constraint (NOT NULL) on column 'strings' even though in the main table strings allows null.
My business requirement is to allow null strings. How can I override this behavior in envers?
The short answer is you can't.
Envers does not treat an element-collection in the same way which ORM does. Instead Envers constructs an entirely separate entity mapping for that element-collection and supplies said mapping to ORM via HBM.
There are a couples reasons we do this but the main reason is that it allows users to configure a mapping such that the element-collection might be audited where-as the remainder of the owning entity is not. ANd because the mapping we supply is an entity-mapping here for the collection, HBM mandates that we must provide ORM with a primary key configuration for it.
We cannot possibly exclude the string-value from the primary key from Envers perspective because you will have multiple values in the collection associated with the same revision number, so some value must differentiate the different rows in the table accordingly.
That said, the only workaround I can see is to use a #OneToMany and wrap your string in an actual entity maping with a generated-id rather than an #ElementCollection if the values need to be audited.
If you decide the values aren't worth auditing, then you can place #NotAudited on the element-collection to also bypass the problem.
Nanos- Would it be possible to use the order-column (if provided) as the primary-key instead of the element-value as this is a source point for my project as well.
Thanks
I have seen examples of setting up Map association with EBean. Examples I've seen typically support a schema as below.
With such a schema, the JPA mapping of Video would have a property public List<VideoMetadata> metadata;.
How can I change this so that metadata is a Map instead of a List, for instance public Map<String, String> metadata;?
CREATE TABLE video (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL
);
CREATE TABLE metadata (
id BIGSERIAL PRIMARY KEY,
video_id UUID NOT NULL, -- this relationship is not polymorphic
key VARCHAR(255) NOT NULL,
value VARCHAR(255) NOT NULL
);
I have defined a polymorphic relationship between Metadata and Video (and Photo, and on...). A Video can have multiple metadata, as can a Photo.
The polymorphic relationship is configured with InheritanceType.SINGLE_TABLE and the "describable" is identified via a combination of describable_type and describable_id.
The database schema looks like this.
CREATE TABLE video (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL
);
CREATE TABLE metadata (
id BIGSERIAL PRIMARY KEY,
describable_id UUID NOT NULL, -- now we have polymorphism
describable_type VARCHAR(255) NOT NULL, -- now we have polymorphism
key VARCHAR(255) NOT NULL,
value VARCHAR(255) NOT NULL
);
And the models (in case it's relevant, I'm using Ebean)...
package models;
import io.ebean.Model;
import javax.persistence.Id;
import java.util.UUID;
import play.data.validation.Constraints;
import javax.persistence.*;
import java.util.List;
#Entity
public class Video extends Model {
#Id
public UUID id;
#Constraints.Required
public String title;
#ElementCollection(fetch = FetchType.EAGER)
#OneToMany(mappedBy = "describable", cascade = CascadeType.ALL)
public List<VideoMetadata> metadata;
}
package models;
import io.ebean.Model;
import play.data.validation.Constraints;
import javax.persistence.*;
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name = "describable_type",
discriminatorType = DiscriminatorType.STRING
)
public abstract class Metadata extends Model {
#Id
#GeneratedValue
public Long id;
#Constraints.Required
public String key;
#Constraints.Required
public String value;
}
package models;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
#Entity
#DiscriminatorValue("video")
public class VideoMetadata extends Metadata {
#ManyToOne(optional = false)
public Video describable;
}
package models;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
#Entity
#DiscriminatorValue("photo")
public class ImageMetadata extends Metadata {
#ManyToOne(optional = false)
public Photo describable;
}