Being new to ORM, I'd like to find a way to define a simple (meaning without an additional entity) mapping for a list (or a set) of strings within an entity. I found this sample:
import java.util.Set;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
#ElementCollection
#CollectionTable(name = "tags")
private Set<String> tags;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<String> getTags() {
return tags;
}
public void setTags(Set<String> tags) {
this.tags = tags;
}
}
which seems to fit my needs. However, processing this class with Eclipse's hibernate3-maven-plugin:2.2:hbm2ddl, I end up with the following error:
[ERROR] Failed to execute goal
org.codehaus.mojo:hibernate3-maven-plugin:2.2:hbm2ddl (default) on
project test-database: Execution default of goal
org.codehaus.mojo:hibernate3-maven-plugin:2.2:hbm2ddl failed: Could
not determine type for: java.util.Set, at table: Book, for columns:
[org.hibernate.mapping.Column(tags)] -> [Help 1]
Specifying #ElementCollection(targetClass=String.class) did not help. Adding a column definition to the tags field (#Column(name = "tags", columnDefinition="character varying (255)", nullable = false)) leads to a successful build but produces this SQL:
create table Book (
id int8 not null,
tags character varying (255) not null,
primary key (id)
);
which is not what I want, as I was expecting to end up with a tags table linked to the books table. Could someone point me to the right direction ? Thank you.
#ElementCollection has been introduced in JPA v 2.0: the mapping you've done is correct. However make sure the maven hibernate plugin you use is at the correct version. Hibernate itself is compliant with JPA 2.0 starting at version 3.5.
You should create a Tag table : tag_id pk, book_id fk references id from book and any other columns you want
and map you list with something like:
#OneToMany(mappedBy="book",fetch=FetchType.LAZY, cascade=CascadeType.REMOVE)
create your tag class with all your columns from database+ a Book object named book for exemple and map this book with:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="id",insertable=false,updatable=false)
Related
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!
I've been getting this "SQLSyntaxErrorException:Unknown column 'product0_.return_policy' in 'field list'" when I try to run the URL on my browser to GET all the products.
Look here
The browser shows the below as well:
There was an unexpected error (type=Internal Server Error, status=500).
could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
returnPolicy is the only variable that's causing this problem. I'm able to successfully retrieve all the values from the database when I remove the variable itself from both the database and from the Product class in Java.
This is the getAllProducts method that is part of the RESTController:
#RequestMapping(method=RequestMethod.GET, value="/products")
public List<Product> getAllProducts() {
return productService.getAllProducts();
}
which works fine when I remove the returnPolicy variable altogether.
This is the MySQL table description:
Values stored in returnPolicy column:
returnPolicy
0
0
1
1
1
This is the code for the 'Product' model's variables:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
private String vendor;
private String description;
private Boolean returnPolicy;
ProductRepository
#Repository public interface ProductRepository extends JpaRepository<Product, String>{ }
Is there a problem with the mapping between SQL tinyint(Boolean) and the Boolean type of Java?
Hibernate is assuming that the entity field returnPolicy corresponds to the table column return_policy. But actually, the column name is returnPolicy
Hibernate follows a naming strategy as to what column name should it derive from entity field names. You should explicitly specify whether to use ImplicitNamingStrategy or PhysicalNamingStrategy. Hibernate provides out of the box classes for this.
Alternatively, for this specific issue, annotating the field with explicit column name will make is understand.
See this SO answer for more.
Your returnPolicy is not mapped with column return_policy you can quick fix this with following
Method1
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
private String vendor;
private String description;
#Column(name = "return_policy")
private Boolean returnPolicy;
Method2
#JsonNaming annotation is manage naming strategy as to what column name should it derive from entity field names.
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
private String vendor;
private String description;
private Boolean returnPolicy;
You can use this annotation #Transient if you don't use attribute as column in database
#Transient annotation in JPA or Hibernate is used to indicate that a field is not to be persisted or ignore fields to save in the database. #Transient exist in javax. persistence package. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.
You can use it in this way .
#Transient
private String token;
Hibernate converts the camel case lettering to underscores by default. so you either change your columns in your table to reflect that or change hibernate naming strategy.
Add the below in the application.properties file:
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
I need to create entity based on information from database. Based on database I created string like this :
` package az.com.ds.entity.crudEntity;
import javax.persistence.Table;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Column;
#Table(name = "CMN_SP", schema = "CMN")
#Entity
public class CmnSpEnt {
#Id
private Integer id;
#Column(name = "NAME")
private String name;
} `
Then I created java file based on this string and compiled it at runtime. Everything works perfectly to this step. But when I want to get data based on entity it throws exception as
org.hibernate.hql.internal.ast.QuerySyntaxException: CmnSpEnt is not mapped [Select x from CmnSpEnt x ].
Now I need to map entity for hibernate in order to get data from database. Is there a way to accomplish this?
I still learning about Ebean ORM with Play Framework. have problem with unexpected evolution script that generated by Play!Framework. I'm using Play!Framework 2.1.1 with JDK 1.7 update 5 64-bit. Sorry, for long code snippet in this question.
I have two Ebean Model looks like following:
Course.java
package models;
import play.data.validation.Constraints;
import play.db.ebean.Model;
import javax.persistence.*;
#Entity
#Table(name = "castillo_courses")
public class Course extends Model {
public enum CourseType {
COMPULSORY(1), BASIC_INTEREST(2), ADVANCED_INTEREST(3), THESIS(4);
private int value;
CourseType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
#Id
private String code;
#Constraints.Required
private String course_name;
#Constraints.Required
private String credits;
#Constraints.Required
private CourseType course_type;
// Ebean finder and Other getter and setter method
......
}
CourseInterest.java
package models;
import play.data.validation.Constraints;
import play.db.ebean.Model;
import javax.persistence.*;
#Entity
#Table(name = "castillo_course_interest")
public class CourseInterest extends Model {
public enum InterestType {
ARCHITECTURAL_INFRA(1), SOFTWARE_TECH(2), INFORMATION_PROCESSING(3), ENTERPRISE_SYSTEM(4), COMP_INTELLIGENCE(5);
private int value;
InterestType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
#Id
#ManyToOne
#JoinColumn(name = "course_code", referencedColumnName = "code")
private Course course;
#Id
#Constraints.Required
private InterestType interest_type;
// Ebean finder and Other getter and setter method
......
}
This is generated evolution script from the models above:
# --- Created by Ebean DDL
# To stop Ebean DDL generation, remove this comment and start using Evolutions
# --- !Ups
create table castillo_courses (
code varchar(255) not null,
course_name varchar(255),
credits varchar(255),
course_type integer,
constraint ck_castillo_courses_course_type check (course_type in (0,1,2,3)),
constraint pk_castillo_courses primary key (code))
;
create table castillo_course_interest (
course_name varchar(255),
credits varchar(255),
course_type integer,
interest_type integer not null,
constraint ck_castillo_course_interest_course_type check (course_type in (0,1,2,3)),
constraint ck_castillo_course_interest_interest_type check (interest_type in (0,1,2,3,4)))
;
create sequence castillo_courses_seq;
create sequence castillo_course_interest_seq;
# ..... !DOWNS code not shown
What I expected with the generated evolution script is:
In castillo_courses CREATE TABLE script, ck_castillo_courses_course_type constraint should check in (1,2,3,4) as defined by CourseType.value attribute, not to check in (0,1,2,3). I suspect evolution generated this check by using ORDINAL value of my Enums.
In castillo_course_interest CREATE TABLE script, it define again all castillo_courses fields except code. I expect the script is only define course_code column as defined by #JoinColumn annotation. There is another problem here. It has no script to generate primary key constraint too, because I have defined two #Id defined in model.
I appreciate to anyone that can explain, give advice, or help me with this problem.. :)
Kindly regards.
user the #EnumValue("1")
sample.
If all the values are parsable as Integers then Ebean will persist and fetch them as integers rather than strings.
public enum InterestType {
#EnumValue("1")
ARCHITECTURAL_INFRA(1),
#EnumValue("2")
SOFTWARE_TECH(2),
#EnumValue("3")
INFORMATION_PROCESSING(3),
#EnumValue("4")
ENTERPRISE_SYSTEM(4),
#EnumValue("5")
COMP_INTELLIGENCE(5);
private int value;
InterestType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
For question number 1, I used suggestion from #publiclass1.
For question number 2, I learn about Compound Primary Key. On CourseInterest model, I used the Compound Primary Key because I want it to have 2 type of primary key, one is the foreign key (course_code) and the other is a common field (interest_type). So, I tried like following.
This is sample of CourseInterest model :
#EmbeddedId // using compound primarykey
public CourseInterestPK key;
#MapsId("courseCode") // map embedded key
#ManyToOne
#JoinColumn(name = "course_code", referencedColumnName = "code")
public Course course;
#MapsId("interestType") // map embedded key
#Constraints.Required
public InterestType interest_type;
This is sample of CourseInterestPK (Compound Primary Key definition) class:
#Embeddable
public class CourseInterest15541120PK {
#Column(name = "course_code")
public String courseCode;
#Column(name = "interest_type")
public CourseInterest.InterestType interestType;
#Override
public boolean equals(Object obj) {
... // MUST to override this method
}
#Override
public int hashCode() {
... // MUST to override this method
}
}
So, with these technique, I get the evolution script that I want to. ;)
I'm looking for a way to set the "default" mapping that Hibernate applies to a variable name in a Java object to query it against the database. At the moment we are using the inline javax.persistence markup to manually set column names, but since we have a rigid naming policy for our database it would be nice to be able to just skip on the manual naming and let Hibernate do the mapping. However, at the moment this doesnt work nice at all with anything save for local, non-primary key fields.
At the moment, Hibernate seems to be set to map non-foreign keys to just their name (see "foo" in the below example class), and foreign-keys to "variableName_ReferencedTable_Id" (see "bar" in the below example class). We would like non-foreign keys to stay as they are, except for the variable marked #id, which we would like to be mapped to "TableName_Id", and we would like foreign keys to be mapped to "variableName_Id". Is this even possible, or do we just have to put up with manual mapping?
package testPackage.test
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.ManyToOne;
#Entity
public class Table1 {
private int id;
private int localVariable;
private int foreignKeyVariable;
// Constructor here somewhere
// Without a #Column( name="Table1_Id" ), this comes out as "id".
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// This works fine, being a local field.
public int getLocalVariable() {
return localVariable;
}
public void setLocalVariable(int LocalVariable) {
this.localVariable = localVariable;
}
// Withou a #JoinColumn( name="foreignKeyVariable_Id" ) , this comes out as "foreignKeyVariable_Table2_Id".
#ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
#JoinColumn() // Not sure if this would even be necessary at all. Happy to leave it out if possible.
public int getForeignKeyVariable() {
return foreignKeyVariable;
}
public void setForeignKeyVariable(int foreignKeyVariable) {
this.foreignKeyVariable = foreignKeyVariable;
}
}
(copied from comment)
Hibernate does have the concept of NamingStrategy, but it's not sensitive to whether than object is a PK or a normal column, so that's not going to be of any use.