I have an issue related with that JPA enity not saved in database, so
after added new clob field in jpa entity - entity doesn't save in database
I use websphere, spring 4.3.13 and Oracle11g
my jpa enity before
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.envers.Audited;
import javax.persistence.*;
import java.sql.Timestamp;
#Entity
#Audited
#Table(name = "UserRecord")
#NoArgsConstructor(access = AccessLevel.PUBLIC)
#AllArgsConstructor
#Getter
#Setter
#Access(AccessType.FIELD)
public class UserRecord {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "PR_KEY")
private String prKey;
//Business Key
#Column(name = "businessId", length = 100, unique = false)
private String businessId;
//Business Key
#Column(name = "name", length = 100, nullable = false)
private String name;
//Business Key
#Column(name = "surname", length = 100, nullable = false)
private String surname;
#Column(name = "updateDateTime", length = 50, nullable = false)
private Timestamp updateDateTime;
#Version
private int version;
#Column(name = "user", length = 100000)
#Lob
private byte[] user;
public UserRecord(String businessId, String name, String surname, Timestamp updateDateTime, byte[] user) {
this.businessId = businessId;
this.name = name;
this.surname = surname;
this.updateDateTime = updateDateTime;
this.user = user;
}
}
and after
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.envers.Audited;
import javax.persistence.*;
import java.sql.Timestamp;
#Entity
#Audited
#Table(name = "UserRecord")
#NoArgsConstructor(access = AccessLevel.PUBLIC)
#AllArgsConstructor
#Getter
#Setter
#Access(AccessType.FIELD)
public class UserRecord {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "PR_KEY")
private String prKey;
//Business Key
#Column(name = "businessId", length = 100, unique = false)
private String businessId;
//Business Key
#Column(name = "name", length = 100, nullable = false)
private String name;
//Business Key
#Column(name = "surname", length = 100, nullable = false)
private String surname;
#Column(name = "updateDateTime", length = 50, nullable = false)
private Timestamp updateDateTime;
#Version
private int version;
#Column(name = "user", length = 100000)
#Lob
private byte[] user;
#Column(name = "userXml")
#Lob
private String userXml;
public UserRecord(String businessId, String name, String surname, Timestamp updateDateTime, byte[] user, String userXml) {
this.businessId = businessId;
this.name = name;
this.surname = surname;
this.updateDateTime = updateDateTime;
this.user = user;
this.userXml = userXml;
}
}
serice layer code fragment is
before
record = new RiskMetricRecord(businessId, name, surname, updateDateTimeTimestamp, serialize(userToSave));
after
record = new RiskMetricRecord(businessId, name, surname, updateDateTimeTimestamp, serialize(userToSave), userToSave);
And my Jparepository is:
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public interface UserRepository extends CrudRepository<UserRecord, String> {
}
So, when I try to save my entity in database after added field #Lob String in JPA entity - there is no error messages, but, in database there is no changes too..
More info:
database table is
-- auto-generated definition
CREATE TABLE USERRECORD
(
PR_KEY VARCHAR2(255 CHAR) NOT NULL
PRIMARY KEY,
BUSINESSID VARCHAR2(100 CHAR),
USER BLOB,
NAME VARCHAR2(100 CHAR) NOT NULL,
SURNAME VARCHAR2(100 CHAR) NOT NULL,
VERSION NUMBER(10) NOT NULL,
UPDATEDATETIME TIMESTAMP(3),
USERXML CLOB
)
/
Tried:
1)
#Lob
#Column(name = "userXml", columnDefinition = "CLOB")
private String userXml;
When I worked on persisting BLOB in app, I tried byte[], it did not save the data. Then I changed data type of property in my entity class to java.sql.Blob, which fixed the problem.
In your case, you are working with CLOB, then change data type of properties from String or byte[] to java.sql.Clob.
#Column(name = "user", length = 100000)
#Lob
private Clob user;
#Column(name = "userXml")
#Lob
private Clob userXml;
So before your persist entity, you have to transform payload of user and userXml properties to Clob objects.
To create Clob object, you can use Hibernate utilities.
Hibernate.getLobCreator(getHibernateSession()).createClob(stringvar);
in JPA, you can easily use TEXT property as mentioned here, it will store any amount of data. Also, no need to define length manually. And also delete #Lob, there is no need. Keep in mind that first delete your column at DB and re-create to update its definition.
#Column(name = "userXml", columnDefinition="TEXT")
private String userXml;
Related
I am still working on my very first solo spring boot project. It is suppose to be a Rest API using the MariaDB example database Nation. There is the country_languages table which receives two foreign keys that also are the primary keys and has another regular field. First foreign key is the id from countries table and the second one is the id from languages table. When I use the save() method in order to create a new tuple I get this error:
org.springframework.dao.InvalidDataAccessApiUsageException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer; nested exception is java.lang.IllegalArgumentException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer
This is the country_languages table from the MariaDB example:
create table country_languages(
country_id int,
language_id int,
official boolean not null,
primary key (country_id, language_id),
foreign key(country_id)
references countries(country_id),
foreign key(language_id)
references languages(language_id)
);
I am using an #Embeddable class CountryLanguagesId in order to make a composite key as I found in this reference.
#Embeddable
public class CountryLanguagesId implements Serializable {
#Column(name = "country_id")
private Integer countryId;
#Column(name = "language_id")
private Integer languageId;
public CountryLanguagesId() {
}
public CountryLanguagesId(Integer countryId, Integer languageId) {
this.countryId = countryId;
this.languageId = languageId;
}
// + getters and setters
After that I created the entity for the country_languages table and its repository:
#Entity
#Table(name = "country_languages")
public class CountryLanguages {
#EmbeddedId
CountryLanguagesId countryLanguagesId = new CountryLanguagesId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("countryId")
#JoinColumn(name = "country_id")
private Countries countries;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("languageId")
#JoinColumn(name = "language_id")
private Languages languages;
#Column(name = "official", length = 1, nullable = false)
private Integer official;
public CountryLanguages() {
}
public CountryLanguages(Countries country, Languages language, Integer official) {
this.countries = country;
this.languages = language;
this.official = official;
}
// + getters and setters
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, Integer> {
}
There are the countries and languages entities:
#Entity
#Table(name = "countries")
public class Countries {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id", length = 11, nullable = false)
private Integer countryId;
#Column(name = "name", length = 50, nullable = true)
private String name;
#Column(name = "area", nullable = false)
private BigDecimal area;
#Column(name = "national_day", nullable = true)
private java.sql.Date nationalDay;
#Column(name = "country_code2", length = 2, nullable = false)
private String countryCode2;
#Column(name = "country_code3", length = 3, nullable = false)
private String countryCode3;
#OneToMany(mappedBy = "countries", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Countries() {
}
public Countries(String name, BigDecimal area, Date nationalDay, String countryCode2, String countryCode3) {
this.name = name;
this.area = area;
this.nationalDay = nationalDay;
this.countryCode2 = countryCode2;
this.countryCode3 = countryCode3;
}
#Entity
#Table(name = "languages")
public class Languages {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", length = 11, nullable = false)
private Integer languageId;
#Column(name = "language", length = 50, nullable = false)
private String language;
#OneToMany(mappedBy = "languages", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Languages() {
}
public Languages(String language) {
this.language = language;
}
public Integer getLanguageId() {
return languageId;
}
These are the entries I do when get the error:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
public class ICountryLanguagesJpaRepositoryTest {
#Autowired
private ICountriesJpaRepository countries;
#Autowired
private ILanguagesJpaRepository languages;
#Autowired
private ICountryLanguagesJpaRepository repository;
#Test
public void shouldSaveAndRemoveContinents() {
Countries patu = new Countries("Patu", new BigDecimal(67822.34), new Date(12321233232L), "PU", "PTU");
countries.save(patu);
Languages patuano = new Languages("Patuano");
languages.save(patuano);
CountryLanguages pLanguages = new CountryLanguages(patu, patuano, 0);
repository.save(pLanguages);
assertEquals(1, repository.findAll().size());
System.out.println(repository.findAll());
repository.deleteById(1);
assertEquals(0, repository.findAll().size());
}
I am doing this using a H2 database. Here is the complete debug console output. Sorry but cant paste it here due characters limitation.
Thanks!
Your repository definition is wrong. You should specify the embeddable type as primary key type:
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, CountryLanguagesId> {
}
I have an Entity class as seen below
NB: The checkNumber is unique.
package tz.go.ega.biometic.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.ToString;
import org.hibernate.annotations.NaturalId;
import org.hibernate.validator.constraints.SafeHtml;
import org.springframework.data.annotation.Transient;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Entity
#Table(name = "employee", uniqueConstraints = {
#UniqueConstraint(columnNames = {"check_number"})
})
#Data
#AllArgsConstructor
#ToString
#NoArgsConstructor
public class Employee implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#Basic(optional = true)
//#NotEmpty(message = "Please enter first name")
#Column(name = "first_name")
private String firstName;
#Basic(optional = true)
//#NotEmpty(message = "Please enter middle name")
#Column(name = "middle_name")
private String middleName;
#Basic(optional = true)
//#NotEmpty(message = "Please enter last name")
#Column(name = "last_name")
private String lastName;
private String status;
#Basic(optional = true)
// #Pattern(regexp ="^[a-zA-Z0-9_]*$",message = "{field.validation.voteCode}")
// #SafeHtml(message = "{field.validation.voteCode}")
#Column(name = "vote_code", length = 50)
private String voteCode;
#NaturalId
#Basic(optional = true)
//#NotNull(message = "Please enter check number")
#Column(name = "check_number")
private long checkNumber;
private Boolean isActive = true;
#Basic(optional = false)
#Column(name = "created_at", updatable = false)
#DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime createdAt = LocalDateTime.now();
#Column(name = "updated_at")
#DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime updatedAt = LocalDateTime.now();
#Column(name = "email")
private String email;
}
I then have a stored procedure that calculates each employee's working hours then returns the results as seen below.
+--------------+-------+
| checkNumber | Time |
+--------------+-------+
| 1122334455 | 29893 |
| 1234567890 | 15427 |
| 2233445566 | 19745 |
| 6655443322 | 12578 |
+--------------+-------+
What I am trying to achieve is, to map the results ( as seen above ) of the stored procedure to an entity (let's call it EmployeeWorkHours ) and then create a relationship between this Entity and the Employee entity using the checkNumber.
I want the EmployeeWorkHours object to be able to reference it's employee directly like in normal hibernate relationships.
How can I go about this, any help will be much appreciated. Thank you.
On your EmployeeWorkHours entity you need a OneToOne relationship with Employee entity
#OneToOne(optional = false, fetch = FetchType.EAGER)
#JoinColumn(name = "checkNumber", unique = true, nullable = false)
private Employee employee;
In your repository you can write a sql query like this :
#Query(value = "{CALL yourStoredProcedure (:var1, :var2, ..., :varn)}", nativeQuery = true)
int getWorkHours(#Param("var1") String var1, #Param("var2") String var2,...,
#Param("varn") String varn);
And then in your service layer you will just call this method do what else you want and persist it.
Hope it helps
I have next problem
public void batchSave(List<Entity> entities) {
repositoryJpa.save(entities);
}
If entities list contains already persisted entity i got DataIntegrityViolationException.
Is there way how to know which entity violated persisting?
Researching spring data source code and DataIntegrityViolationException i could find any place where wrong entity can be stored.
UPD
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GENERATOR")
#Column(name = "ID", unique = true, nullable = false)
public Long getId() {
return id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "NTRY_TABS_TABH_ID", nullable = false)
public OtherEntity getOtherEntity() {
return otherEntity;
}
#Column(name = "SORT_KEY", nullable = false)
public String getSortKey() {
return sortKey;
}
#Enumerated(EnumType.STRING)
#Column(name = "TYPE", nullable = false)
public OperationType getOperationType() {
return operationType;
}
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "activeEntryEntity")
public SortKeyEntity getSortKeyEntity() {
return sortKeyEntity;
}
#Version
#Column(name = "VERSION", nullable = false, insertable = false)
public Long getVersion() {
return version;
}
}
Use javax validation , and you will be able to do something like this:
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
public class User {
#NotNull(message = "Name cannot be null")
private String name;
#AssertTrue
private boolean working;
#Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int age;
#Email(message = "Email should be valid")
private String email;
// standard setters and getters
}
And then you can validate this like :
Set<ConstraintViolation<User>> violations = validator.validate(user);
Take a look at this:
https://www.baeldung.com/javax-validation
I am developing a spring boot application, i need a proper design help for one of my entity relation.
These are my tables
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
User_ID INT AUTO_INCREMENT PRIMARY KEY,
Email VARCHAR(1024),
Phone BIGINT,
Password VARCHAR(128),
User_Type ENUM ('TEACHER', 'PARENT'),
Status ENUM ('ACTIVE', 'DEACTIVE', 'SUSPENDED')
);
DROP TABLE IF EXISTS teacher;
CREATE TABLE teacher
(
Teacher_ID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(1024),
Email VARCHAR(1024),
Phone BIGINT,
Gender ENUM (0, 1),
Date_Of_Joining INT,
Designation VARCHAR(1024),
Is_Active INT
);
DROP TABLE IF EXISTS parent;
CREATE TABLE parent
(
Parent_ID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(1024),
Email VARCHAR(1024),
Phone BIGINT,
Occupation VARCHAR(1024),
Address VARCHAR(4048)
);
DROP TABLE IF EXISTS student;
CREATE TABLE student
(
Student_ID INT AUTO_INCREMENT PRIMARY KEY,
Student_Name VARCHAR(1024),
Class VARCHAR(16),
Section VARCHAR(16),
Academic_Year VARCHAR(64),
DOB DATE,
Gender ENUM (0, 1),
Parent_ID INT
);
Can anyone please help me creating entity classes for the same. Based on the type of user in user table i need to join with either teacher or parent table. For example if User_Type is TEACHER then i will join with teacher table else i will join user with parent table. Join condition would be email.
Student table has one to one relationship with parent table.
You can create domain models like this
#Entity
#Inheritance(strategy= InheritanceType.JOINED)
public User{
//properties
}
#Entity
#PrimaryKeyJoinColumn(name = "id")
public Teacher extends User{
//properties
}
#Entity
public Parent extends User{
//properties
}
#Entity
public Students {
#GenericGenerator(name = "generator", strategy = "foreign",parameters = #Parameter(name = "property", value = "stock"))
#Id
#GeneratedValue(generator = "generator")
#Column(name = "Parent_ID", unique = true, nullable = false)
private Integer id;
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
public Parent parent;
}
You can use the following classes:
User:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Inheritance(strategy= InheritanceType.JOINED)
#Table(name = "user")
public class User {
#Id
#GeneratedValue
#Column(name = "User_ID")
private Integer userID;
#Column(name = "name", length = 1024)
private String name;
#Column(name = "email", length = 1024)
private String email;
#Column(name = "Phone")
private Long phone;
#Column(name = "Password", length = 128)
private String password;
#Column(name = "User_Type", columnDefinition = "enum ('TEACHER', 'PARENT')")
private String userType;
#Column(name = "Status", columnDefinition = "enum ('ACTIVE', 'DEACTIVE', 'SUSPENDED')")
private String status;
}
Teacher:
#Entity
#Table(name = "teacher")
public class Teacher extends User{
#Id
#GeneratedValue
#Column(name = "teacher_ID")
private Integer teacherID;
#Column(name = "name", length = 1024)
private String name;
#Column(name = "email", length = 1024)
private String email;
#Column(name = "Phone")
private Long phone;
#Column(name = "Gender", columnDefinition = "BOOLEAN")
private Boolean gender;
#Column(name = "Date_Of_Joining")
private Integer dateOfJoining;
#Column(name = "Designation", length = 1024)
private String designation;
#Column(name = "Is_Active")
private Integer isActive;
}
Parent:
#Entity
#Table(name = "parent")
public class Parent extends User{
#Id
#GeneratedValue
#Column(name = "Parent_ID")
private Integer parentID;
#Column(name = "name", length = 1024)
private String name;
#Column(name = "email", length = 1024)
private String email;
#Column(name = "Phone")
private Long phone;
#Column(name = "Occupation", length = 1024)
private String occupation;
#Column(name = "Address",length=4048)
private String address;
}
Student:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue
#Column(name = "Student_ID")
private Integer studentID;
#Column(name = "student_name", length = 1024)
private String studentName;
#Column(name = "study_class", length = 16)
private String studyClass;
#Column(name = "section", length = 16)
private String section;
#Column(name = "academic_year",length = 64)
private String academicYear;
#Temporal(TemporalType.DATE)
#Column(name = "dob", length = 4048)
private Date dob;
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "parent")
public Parent parent;
}
Try them out and see if that helps.
One Group has many Users:
Group
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.*;
import java.util.Collection;
import java.util.List;
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#Column(name = "ID")
private Long ID;
#Column(name = "NAME")
private String NAME;
//#JsonManagedReference
#OneToMany(mappedBy = "group"
//, fetch = FetchType.EAGER
//, cascade = CascadeType.ALL
)
private List<Users> itsUser;
//getters and setters are omitted for clarity
}
Users
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.persistence.*;
import static javax.persistence.GenerationType.SEQUENCE;
#Entity
#Table(name = "USERS")
#SequenceGenerator(name = "SEQUENCE_USER_ID", //my own name in java (unique)
sequenceName = "GENERATOR_SEQUENCE_USERS", //in database
initialValue = 1,
allocationSize = 1)
public class Users {
#JsonProperty(value = "id") //these play a role when both reading or writing
#Id
#Column(name = "ID")
#GeneratedValue(strategy=SEQUENCE, generator="SEQUENCE_USER_ID")
private Long ID;
#JsonProperty(value = "name")
#Column(name="NAME")
private String NAME;
#JsonProperty(value = "username")
#Column(name="USERNAME")
private String USERNAME;
#JsonProperty(value = "password")
#Column(name="PASSWORD")
private String PASSWORD;
#JsonProperty(value = "email")
#Column(name="EMAIL")
private String EMAIL;
#JsonProperty(value = "picture") //Now it works with both mypic and picture as json keys
#Column(name="PICTURE")
private String PICTURE;
//#Column(name="GROUP_ID") //we already have a ManyToOne for this, we cannot repeat it
#JsonProperty(value = "groups_id")
//to ignore it in jpa (http://stackoverflow.com/questions/1281952/jpa-fastest-way-to-ignore-a-field-during-persistence)
private Long itsGroupId;
#Transient
public Long getItsGroupId() {
if(itsGroupId == null) {
this.itsGroupId = group.getID();
} else {
//nop
}
return itsGroupId;
}
public void setItsGroupId(Long itsGroupId) {
this.itsGroupId = itsGroupId;
}
//#JsonIgnore
//#JsonProperty(value = "groups_id")
//#JsonBackReference
#ManyToOne(optional = false, targetEntity = Group.class)
#JoinColumn(
name = "GROUP_ID", //column name
referencedColumnName = "ID" //reference name
)
private Group group;
//getters and setters are omitted for clarity
}
We are using Spring with Spring-data and Jackson to do things automagically but we cannot configure the magic:
We are trying to stick on the following constraints at the same time:
1) Keep the ability to have a reference to the groupId and the ManyToOne relationship group.
This is easy to be achieved by putting #Transient annotation at the groupId because #Column is not allowed since we have already declared the #ManyToOne annotation. You also have to implement the getGroupId method accordingly.
2) Return a json of Users class that contains the groups_id.
This can be implemented by setting the #JsonProperty annotation.
3) Create a user class, and also save it in the database, by a json. The json contains groups_id which has as a value an integer for the foreign key.
This does not work because by setting it #Transient above, then the system refuses to save in the database something that is transient or at least this is how we interpret this exception:
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: not-null
property references a null or transient value: com.pligor.mvctest.models.Users.group;
nested exception is org.hibernate.PropertyValueException:
not-null property references a null or transient value: com.pligor.mvctest.models.Users.group
On the backend do something like this:
Group group = groupRepository.findById(userResource.getGroupId());
if (group != null) {
User user = new User(userResource);
user.setGroup(group);
userRepository.save();
}
The idea behind this is that you need to fetch the group from the DB, to be able to link it with the newly created User