JPA: Delete orphans, invalid column - java

Recently I have asked a very similar question on Stack overflow which turned out to be a duplicate of another question. In that other question there was a workaround which I applied and solved my problem. Now, this time the workaround doesn't work, and all other mentioned solutions don't work. Also all the solutions from other threads linked to the first thread don't work.
This was my question at first:
SQLServerException: Invalid column name
And this was the duplication:
hibernate column name issues
I have checked the topics on the right in the Linked and Related sections but can't find an solution to my problem. I also cannot comprehend the reason why my problem occurs.
I have 2 tables: Declaration and File (I won't mention my other tables here because they are irrelevant to the problem)
CREATE TABLE [dbo].[Declaration] (
[number] INT NOT NULL,
[status] VARCHAR (50) NOT NULL,
[name] VARCHAR (50) NOT NULL,
[description] VARCHAR (250) NOT NULL,
[amount] FLOAT (53) NOT NULL,
[date] DATE NOT NULL,
[period_id] INT NOT NULL,
[client_project_id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([number] ASC),
CONSTRAINT [fk_client_period] FOREIGN KEY ([client_project_id]) REFERENCES [dbo].[ClientProject] ([number]),
CONSTRAINT [fk_period] FOREIGN KEY ([period_id]) REFERENCES [dbo].[Period] ([number])
);
CREATE TABLE [dbo].[File] (
[number] INT NOT NULL,
[path] VARCHAR (50) NOT NULL,
[declaration_id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([number] ASC),
CONSTRAINT [fk_file] FOREIGN KEY ([declaration_id]) REFERENCES [dbo].[Declaration] ([number])
);
With the corresponding classes:
#Entity
#Table(name = "[file]")
public class File {
#Id
private int number;
private String path;
#ManyToOne(targetEntity = Declaration.class)
private int declaration_id;
public int getDeclaration_id() {
return declaration_id;
}
public void setDeclaration_id(int declaration_id) {
this.declaration_id = declaration_id;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
And
#Entity
public class Declaration {
#Id
private int number;
private String status;
private String name;
private String description;
private double amount;
private Date date;
private int period_id;
private int client_project_id;
#OneToMany(targetEntity = File.class,mappedBy = "declaration_id",orphanRemoval = true)
private List<File> files = new ArrayList<>();
public List<File> getFiles() {
return files;
}
public void setFiles(List<File> files) {
this.files = files;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number= number;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public int getPeriod_id() {
return period_id;
}
public void setPeriod_id(int period_id) {
this.period_id = period_id;
}
public int getClient_project_id() {
return client_project_id;
}
public void setClient_project_id(int client_project_id) {
this.client_project_id = client_project_id;
}
}
I have defined my #ManyToOne and #OneToMany relations based on these topics and tutorials:
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
JPA JoinColumn vs mappedBy
What I want: Delete Declaration, automatically delete files related to the declaration
What I get: Invalid column name 'declaration_id_number'.
What I have tried:
- renaming fields in database to declaration_id_number (results in declaration_id_number_number)
- using #Column(name="declaration_id") on declaration_id field
- using #Colum(name="declaration_id") on the getter field
- using #JoinColumn(name="fk_file") on the declaration_id field
- Using different kinds of naming stategies (in application.properties), including the default one
spring.jpa.hibernate.naming.strategy: org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
The actual SQL query:
select files0_.declaration_id_number as declarat3_3_0_, files0_.number as number1_3_0_, files0_.number as number1_3_1_, files0_.declaration_id_number as declarat3_3_1_, files0_.path as path2_3_1_ from [file] files0_ where files0_.declaration_id_number=?
select declaratio0_.number as number1_2_0_, declaratio0_.amount as amount2_2_0_, declaratio0_.client_project_id as client_p3_2_0_, declaratio0_.date as date4_2_0_, declaratio0_.description as descript5_2_0_, declaratio0_.name as name6_2_0_, declaratio0_.period_id as period_i7_2_0_, declaratio0_.status as status8_2_0_ from declaration declaratio0_ where declaratio0_.number=?
I am running Spring boot with JPA Hibernate 5.2.10
Is there anyone out there who knows why this happends, if I know why it happends I might be able to fix the problem my self. Right now I am completely stuck.
Thanks in advance.
EDIT:
Ok, so by accident I solved my own problem, I still don't know why the problem occured in the first place. According to this answer(s) of this topic:
JPA JoinColumn vs mappedBy
You use #ManyToOne & #OnyToMany
In my case I don't need to use #ManyToOne in the File class. I only need #OneToMany in my Declaration class. No more errors occur after I removed this annotation.
If anyone knows the reason for this problem, please provide an answer so that it can be of use in the future for me or someone else.

In my case I don't need to use #ManyToOne in the File class. I only need #OneToMany in my Declaration class. No more errors occur after I removed this annotation.
I don't think that this will work. If you remove the #ManyToOne annotation, the persistence provider will create a join table by default to maintain the relationship. What you mean is probably that you don't get any exception. But look at the database schema:
CREATE TABLE [dbo].[File] (
[number] INT NOT NULL,
[path] VARCHAR (50) NOT NULL,
[declaration_id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([number] ASC),
CONSTRAINT [fk_file] FOREIGN KEY ([declaration_id]) REFERENCES [dbo].[Declaration] ([number])
);
declaration_id is declaraed to be NOT NULL which means you cannot save anything in this table unless you assign it an entry in the Declaration table.
You have defined a foreign key constraint which means your database will check this when you save a file record.
This means that you have two options:
you need an #ManyToOne annotation so that JPA can map the entities correctly and automatically that will correspond to your database schema, or
you remove the foreign key field declaration_id and the corresponding referential integrity constraint from the File table. In this case, the persistence provider will create a join table by default for you, unless you customize it.
So if you want to use the first option, i.e. #ManyToOne annotation, you have to map the entities as follows:
#Entity
#Table(name = "[file]")
public class File {
#Id
private int number;
private String path;
#ManyToOne
#JoinColumn(name = "declaration_id")
private Declaration declaration;
public int getDeclaration_id() {
return declaration_id;
}
// ... getters and setters
}
and a slightly modified Declaration entity:
#Entity
public class Declaration {
#Id
private int number;
// ... other fields
#OneToMany(mappedBy = "declaration",orphanRemoval = true)
private List<File> files = new ArrayList<>();
// ... Rest of the code
}
Notes:
I removed targetEntity = File.class attribute from the annotation because you don't need it as your collection already implies the type.
Why are you putting table/column names into the square brackets? They make the code unreadable and I don't see the benefit of using it.

Related

How would I implement Spring Data JDBC Many to Many between 2 tables if I have a third table that contains a foreign key to my bridge table?

I learned that in Spring Data JDBC I need to implement many to many relationships by having a reference to the ID of one entity in the other entity:
public class Student {
#Id
private Long studentId;
private String studentName;
#MappedCollection(idColumn = "student_id", keyColumn = "course_id")
private Set<CourseRef> courses;
}
public class Course {
#Id
private Long courseId;
private String courseName;
}
#Table("student_course")
public class CourseRef {
#Id
private Long studentCourseId;
private Long courseId;
#MappedCollection(idColumn = "student_course_id", keyColumn = "test_score_id")
private List<TestScore> testScores;
}
public class TestScore {
#Id
private Long testScoreId;
private Integer value;
}
public interface StudentRepository implements CrudRepository<Student, Long> {
}
public interface CourseRepository implements CrudRepository<Course, Long> {
}
public class StudentRepositoryTest {
#Autowired
StudentRepository repository;
#Test
void testAddTestScore() {
Student student = repository.findById(1L).get();
assertNotNull(student);
Set<CourseRef> courses = student.getCourses();
CourseRef course = courses.stream().filter(c -> c.getCourseId() == 2).findFirst().orElse(null);
assertNotNull(course);
courses.remove(course);
course.addTestScore(TestScore.create(90);
courses.add(course);
students.setCourses(courses);
repository.save(student);
}
}
With this setup I have a student table, course table, student_course table, and test_score table that has a foreign key to a student_course id. But I'm having trouble adding a new test score. The repository is unable to save the updated student due to a foreign key constraint failure with the student_course_id column. I was wondering, is it possible to add new test scores with this approach, and if so would I need to create a new repository?
You didn't post your schema so I don't know what exactly went wrong, but I started with your code and created a working example from it: https://github.com/schauder/stackoverflow/tree/main/jdbc/three-way-reference
I took the liberty to simplify your property/column names since repeating the entity/table didn't add value for me.
The Domain Model
I put some hints of DDD on it, but didn't go the full 9 yards.
But I wanted at least to give some examples on how the logic for adding a TestScore belongs into the aggregate and not out side in the test or service. But this is not relevant for the mapping.
The important changes I made are:
I dropped the ids of CourseRef and TestScore. Ids in general are not neccesary on inner entities (not aggregate roots).
I had to reintroduce it to CourseRef due to a bug I found in the progress.
I simplified the code for adding a TestScore removing and then adding the course again is not necessary.
public class Course {
#Id
Long id;
String name;
static Course create(String name) {
Course course = new Course();
course.name = name;
return course;
}
}
class Student {
#Id
final Long id;
String name;
#MappedCollection(idColumn = "STUDENT_ID", keyColumn = "COURSE_ID")
Set<CourseRef> courses = new HashSet<>();
Student(Long id) {
this.id = id;
}
/**
* quality of life method to create students by name.
*/
public static Student create(String name) {
final Student student = new Student(null);
student.name = name;
return student;
}
/**
* The aggregate root should take care of whatever logic is necessary to add a course.
*/
void addCourse(Course course) {
final CourseRef ref = new CourseRef();
ref.courseId = AggregateReference.to(course.id);
courses.add(ref);
}
/**
* The aggregate root should take care of whatever logic is necessary to add a testscore.
* #param course
* #param score
*/
public void addScore(Course course, int score) {
courses.stream()
.filter(c -> c.courseId.getId().equals(course.id))
.findFirst()
.orElseThrow()
.testScores.add(TestScore.create(90));
}
}
#Table("STUDENT_COURSE")
class CourseRef {
#Id // work around for issue https://github.com/spring-projects/spring-data-jdbc/issues/1139
Long Id;
AggregateReference<Course,Long> courseId;
#MappedCollection(idColumn = "STUDENT_COURSE_ID", keyColumn = "INDEX")
List<TestScore> testScores;
}
class TestScore {
private Integer value;
public static TestScore create(int value) {
final TestScore testScore = new TestScore();
testScore.value = value;
return testScore;
}
}
Database Schema
I added one:
CREATE TABLE COURSE
(
ID INTEGER IDENTITY PRIMARY KEY,
NAME VARCHAR(200) NOT NULL
);
CREATE TABLE STUDENT
(
ID INTEGER IDENTITY PRIMARY KEY,
NAME VARCHAR(200) NOT NULL
);
CREATE TABLE STUDENT_COURSE
(
ID INTEGER IDENTITY PRIMARY KEY,
STUDENT_ID INTEGER NOT NULL,
COURSE_ID INTEGER NOT NULL,
UNIQUE (STUDENT_ID, COURSE_ID),
FOREIGN KEY (STUDENT_ID) REFERENCES STUDENT(ID),
FOREIGN KEY (COURSE_ID) REFERENCES COURSE(ID)
);
CREATE TABLE TEST_SCORE
(
STUDENT_COURSE_ID INTEGER,
INDEX INTEGER,
VALUE INTEGER,
PRIMARY KEY (STUDENT_COURSE_ID, INDEX),
FOREIGN KEY (STUDENT_COURSE_ID) REFERENCES STUDENT_COURSE(ID)
);
The Test
#DataJdbcTest
class StudentRepositoryTest {
#Autowired
StudentRepository students;
#Autowired
CourseRepository courses;
Student jens = null;
#BeforeEach
void setup() {
Course physics = courses.save(Course.create("Physics"));
Course math = courses.save(Course.create("Math"));
Course informatics = courses.save(Course.create("Informatics"));
jens = Student.create("Jens");
jens.addCourse(physics);
jens.addCourse(math);
jens.addCourse(informatics);
jens = students.save(jens);
}
#Test
void testAddTestScore() {
Student student = students.findById(jens.id).orElseThrow();
assertNotNull(student);
Course math = courses.findByName("Math");
assertNotNull(math);
student.addScore(math, 90);
students.save(student);
}
}

Inner property is not automatically cast in JOOQ

I have table as below:
CREATE TABLE recipes
(
id INT AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
components JSON,
active BOOLEAN NULL DEFAULT TRUE,
PRIMARY KEY (id),
UNIQUE KEY (name)
)
CHARACTER SET "UTF8"
ENGINE = InnoDb;
I have created pojo class like below:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CValueRecipeV2
{
#JsonProperty("components")
#JsonAlias("matcher.components")
#Column(name = "components")
#Valid
private List<CComponentV2> mComponents;
#JsonProperty("name")
#Column(name = "name")
private String name;
public List<CComponentV2> getComponents()
{
return mComponents;
}
public void setComponents(List<CComponentV2> mComponents)
{
this.mComponents = mComponents;
}
public String getName()
{
return mName;
}
public void setName(String mName)
{
this.mName = mName;
}
}
another class
#JsonIgnoreProperties(ignoreUnknown = true)
public class CComponentV2
{
#JsonProperty("shingle_size")
#JsonAlias("shingleSize")
#CShingleField
private Integer mShingleSize;
public Integer getmShingleSize()
{
return mShingleSize;
}
public void setmShingleSize(Integer mShingleSize)
{
this.mShingleSize = mShingleSize;
}
}
Now I am trying to fetch the record from the database using JOOQ.
But I am not able to convert json component string into component class.
I am reading the data from the table as mentioned below:
context.dsl().select(RECIPES.asterisk())
.from(RECIPES)
.where(RECIPES.NAME.eq(name))
.fetchInto(CValueRecipeV2.class);
In the database, I have the following record.
ID name components active
1 a [{"shingle_size=2"}] true
While fetching the data, I am receiving the following error
Caused by: org.jooq.exception.DataTypeException: Cannot convert from {shingle_size=2} (class java.util.HashMap) to class com.ac.config_objects.CComponentV2
I am new to JOOQ. Please let me know if I missing anything.
Thanks in advance.
I have solved my problem using the jooq converter.
var record = context.dsl().select(RECIPES.asterisk())
.from(RECIPES)
.where(RECIPES.NAME.eq(name))
.fetchOne();
record.setValue(RECIPES.COMPONENTS, record.get(RECIPES.COMPONENTS, new CComponentV2Converter()));
var recipe = record.into(CValueRecipeV2.class);
and my converter lools like below:
public class CComponentV2Converter implements Converter<Object, List<CComponentV2>>
{
static final long serialVersionUID = 0;
#Override
public List<CComponentV2> from(Object databaseObject)
{
var componentList = CObjectCaster.toMapList(databaseObject);
List<CComponentV2> cComponentV2s = new ArrayList<>();
componentList.forEach(e -> {
CComponentV2 cComponentV2 = new CComponentV2();
cComponentV2.setmShingleSize(CObjectCaster.toInteger(e.get("shingle_size")));
cComponentV2s.add(cComponentV2);
});
return cComponentV2s;
}
}
jOOQ doesn't understand your #JsonProperty and other annotations out of the box. You will have to implement your own record mapper to support them:
https://www.jooq.org/doc/latest/manual/sql-execution/fetching/pojos-with-recordmapper-provider/

How to force EntityManager.persist() to do only insert and not update

I am trying to insert data into a table having columns (NAME, VALUE) with
EntityManager.persist().
When I persist an entity like ('xx', 'value1') it inserts a new record into the table for that entity. But if I want to persist a new entity like ('xx', 'value2'), the entity is persisted in the place of already existing record.
The questions are:
Why and how is it happened?
Is there a way to insert ('xx', 'value2') too?
I found a similar question here but there is no real answer for the question.
Many thanks.
UPDATE: The first column is not a primary key. Instead, the second one is.
Here is the Entity:
#Entity
#Table(name = "TEST_DATA")
public class TestDataEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "NAME", nullable = false)
private String name;
#Id
#Column(name = "VALUE", nullable = false)
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
And here is the persisting code:
#Transactional
public static void storeTestData(EntityManager em, String name, String value) {
TestDataEntity entity = new TestDataEntity();
entity.setName(name);
entity.setValue(value);
em.persist(entity);
}
Also, there is another question, which is described here.
The issue is solved this way:
#Transactional
public static void storeTestData(EntityManager em, String name, String value) {
EntityTransaction transaction = em.getTransaction();
try {
transaction.begin();
TestDataEntity entity = new TestDataEntity();
entity.setName(name);
entity.setValue(value);
em.persist(entity);
transaction.commit();
} catch (RuntimeException re) {
if (transaction != null && transaction.isActive()) {
transaction.rollback();
}
throw re;
}
This means, that if no explicit transaction is provided, then the existing record is being updated if it has any value matched with the corresponding field value in entity object.
IMHO, this is really strange and not straightforward, since it would be better if it would throw a
javax.persistence.TransactionRequiredException
like it does on update/delete statements.
Check if your entity correctly implements equals() and hashCode(), usually this solves the problem

Unable insert row using Hibernate if double quotes are used

For fail safe am always using double quotes in my mapping class. And this only for PostgreSQL
Here is my class:
#Entity
#Table(name="`Person`"
,schema="public"
)
public class Person implements java.io.Serializable {
private Integer id;
private String name;
private String address;
public Person() {
}
#Id #GeneratedValue(strategy=IDENTITY)
#Column(name="`ID`", nullable=false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name="`Name`")
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="`Address`")
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
}
When am trying insert using Am getting below exception:
org.postgresql.util.PSQLException: The column nameIDwas not found in this ResultSet.
Query it ran for insert was:
insert into public."Person" ("Address", "Name") values (?, ?)
I can't remove double quotes for other reasons.
Please help me to get this problem fixed.
Update:
Database Schema:
CREATE TABLE "Person"
(
"ID" INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('"Person_ID_seq"'::regclass),
"Name" VARCHAR(255),
"Address" VARCHAR(255)
);
If everything need to be quoted, add the following flags to hibernate.properties or persistence.xml file
hibernate.globally_quoted_identifiers=true
and remove all the single quote from the class Person. However, take note that for Postgresql, placing table/column name etc in double quote effectively turn them into case sensitives. So the case for the table/column in the database must be exact match of the corresponding name in the #Table and #Column annotation.

Hibernate SQLQuery with transient properties

I need to make an hibernate SQLQuery with db2 and this query is returning me some fields which are calculated and have no relation with any columns in database.
The goal is setting the values of these sum() calculations from SQLQuery on three new transient fields in a Java Object which already existed.
The SQLQuery uses the syntax:
SELECT id as {entityObject.id},
name as {entityObject.name},
order as {entityObject.order},
SUM(CASE
WHEN pv.value_id = 1
AND pv.value=1 THEN 1 ELSE 0 END) AS {entityObject.someCount}
The problem is that Hibernate complains and says to need a column for someCount. It seems not to help declaring the java field as transient or even using the #Transient annotation from javax.persistence at the same time.
If I only declare in the hbm.xml mapping file:
<property name="id" type="java.lang.Integer" column="someColumn" />
<!-- Some more fields here -->
<!-- THE IMPORTANT ONE -->
<property name="someCount" type="java.lang.Integer"/>
Java Object:
public class EntityObject implements Serializable {
private static final long serialVersionUID = 1479579608940145961L;
private Integer id;
private String name;
private Integer order;
// This is the one giving me hell. I've tried with #Transient also
private transient Integer someCount;
public Category() {
}
public Category(final String name, final Integer order) {
this.name = name;
this.order = order;
}
public Integer getOrder() {
return this.order;
}
public void setOrder(final Integer order) {
this.order = order;
}
public Integer getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public Integer getSomeCount() {
return someCount;
}
public void setSomeCount(final Integer count) {
this.someCount = count;
}
}
It asks me for a column, and I have tried inserting a fake column and it does not work. The thing is that I want these 'count' fields only to be set from the SQLQuery and to be empty and null when coming from a regular Hibernate Query.
I have looked at the docs and googled, and it seems that you can declare a field transient by only not declaring it at the hibernate mapping file, but then it does not set it on the object with the "as {entityObject.someCount}" even when I have getters/setters declared.
Help please.
Thanks very much in advance.
The only option available that might do all this directly from the Database without having to issue additional queries is a Hibernate Formula property:
http://wiki.jrapid.com/w/Formula_(attribute_of_property)
<property name="someCount" formula="select count(*) from some_table where table_key = ?"/>
The ? placeholder will be populated automatically with the ID of the current instance.
1 Create a POJO:
public class SumValue{
private BigInteger myId;
private String myName;
private BigInteger myOrder;
private BigInteger mySum;
....
getters and setters here
....
}
2 Minor changes in your query
SELECT id as "myId",
name as "myName",
order as "myOrder",
SUM(CASE
WHEN pv.value_id = 1
AND pv.value=1 THEN 1 ELSE 0 END) AS "mySum"
3 Execute native sql
List<SumValue> jobStateViewList = (List<SumValue>)getSessionFactory().getCurrentSession()
.createSQLQuery(yourQuery)
.setResultTransformer(
new AliasToBeanResultTransformer(SumValue.class)
).list();

Categories

Resources