Bidirectional mapping not working since it's not saving correctly - spring

I am new to Spring and I am testing the API on postman and the instructor_id is not saving.
This is how I am testing it on postman and don't know if it is right or not.
{
"coursename":"tesasdasdast",
"description":"tesdasdsadasting",
"userEntity":{
"instructor_id":1
}
}
Steps:
I have two entities:
UserEntity
CourseEntity
I have a controller where I'm saving the information called:
CourseController
I have a repository called CourseRepo and I am extending JpaRepository
In CourseEntity:
#ManyToOne
#JoinColumn(name = "instructor_id")
private UserEntity userEntity;
In UserEntity:
#OneToMany(mappedBy = "userEntity", cascade=CascadeType.ALL)
private List<CourseEntity> courseEntity;
In my CourseController:
#PostMapping("/courses")
void addCourse(#RequestBody CourseEntity course) {
courseRepository.save(course);
}
I have added in my pom.xml:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
This is the error message that I'm getting in postman:
{
"timestamp": "2019-10-10T16:51:47.780+0000",
"status": 500,
"error": "Internal Server Error",
"message": "org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.finaly.projectback.entity.CourseEntity.userEntity -> com.finaly.projectback.entity.UserEntity; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.finaly.projectback.entity.CourseEntity.userEntity -> com.finaly.projectback.entity.UserEntity",
"path": "/project/courses"
}
And these two are the tables in the database:
create table web_user(
id INT primary key auto_increment,
firstname VARCHAR(50),
lastname VARCHAR(50),
username VARCHAR(50),
email VARCHAR(50),
pwd VARCHAR(50),
user_role_id INT,
FOREIGN KEY (user_role_id) REFERENCES web_user_role(id)
);
CREATE TABLE web_course (
id INT PRIMARY KEY AUTO_INCREMENT,
coursename VARCHAR(50),
instructor_id INT,
FOREIGN KEY (instructor_id) REFERENCES web_user(id)
);
Thank you in advance for any advice.

Please check if you are correctly setting entities in associations.
userEntity.setCourseEntity and courseEntity.setUserEntity
Do you have this code in place and then check your hibernate queries that are fired on save method call.

I found my mistake. Instead of creating the foreign key as user_entity_user_id, i was creating it as user_id.
It seems like a stupid mistake but I'm still new to Spring and for now, many things look like magic.
Thanks everyone who tried to help.

Related

Spring Data JDBC aggregate ID in child objects

I was playing with Spring-Data-JDBC and encountered 2 issues. I have following entities with 1:N relationship.
------
DROP TABLE IF EXISTS product;
CREATE TABLE product (
product_id int AUTO_INCREMENT PRIMARY KEY,
name varchar(250) not null,
description varchar(512) not null
);
DROP TABLE IF EXISTS product_line;
CREATE TABLE product_line (
product_id int constraint fk_product_line_product references product(product_id),
label varchar(250) not null
);
----------
#Data
#Builder
public class Product {
#Id
private Long productId;
private String name;
private String description;
#Singular
#MappedCollection(idColumn = "product_id", keyColumn = "product_id")
private Set<ProductLine> lines;
}
#Data
#Builder
public class ProductLine {
private Long productId;
private String label;
}
Problem 1: Following test case fails because I was expecting to have the productId populated in the ProductLine object but it is not. Is this the expected behavior of Spring Data JDBC?
#SpringBootTest
class SpringDataJdbcApplicationTests {
#Autowired
private ProductRepository productRepository;
#Test
void saveTest() {
Product product = Product.builder()
.name("Product-1")
.description("Description")
.line(ProductLine
.builder()
.label("Line-label")
.build())
.build();
this.productRepository.save(product);
assertThat(product.getProductId()).isNotNull();
assertThat(product.getLines()).isNotNull().isNotEmpty().hasSize(1);
assertThat(product.getLines().stream().findFirst()).isPresent();
assertThat(product.getLines().stream().findFirst().get().getProductId()).isNotNull().isEqualTo(product.getProductId()); // -----> Fails here.
}
}
Problem 2: If I change Set<ProductLine> to List<ProductLine>, it fails due to JdbcSQLIntegrityConstraintViolationException, which means the product id set to 0 as seen in the log snippet below.
2022-09-10 22:33:12.393 DEBUG 18460 --- [ main] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [INSERT INTO "PRODUCT_LINE" ("LABEL", "PRODUCT_ID") VALUES (?, ?)]
2022-09-10 22:33:12.393 TRACE 18460 --- [ main] o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 1, parameter value [Line-label], value class [java.lang.String], SQL type 12
2022-09-10 22:33:12.393 TRACE 18460 --- [ main] o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 2, parameter value [0], value class [java.lang.Integer], SQL type 4
Following test case fails because I was expecting to have the productId populated in the ProductLine object but it is not. Is this the expected behavior of Spring Data JDBC?
Yes, if you want a productId you have to (and can easily) populate it yourself using plain Java code.
But you really shouldn't need the productId in the first place since if you follow Domain Driven Design, you will access a ProductLine exclusively from a Product which already has the id at hand.
The article https://spring.io/blog/2021/09/22/spring-data-jdbc-how-do-i-make-bidirectional-relationships might be helpful.
If I change Set<ProductLine> to List<ProductLine>, it fails due to JdbcSQLIntegrityConstraintViolationException, which means the product id set to 0 as seen in the log snippet below.
You have two problems here:
You already have two sources for the product_id field: The relation from the aggregate root and the simple field, which may cause problems.
You mapped both the back reference to the aggregate root idColumn and the index of the list keyColumn to the same database column. Together with the simple field from above these are three values all mapped to the same column. Not good.
The value that seems to win is the list index, resulting in the exception.
In order to fix that, create an additional column in the product_line table and map the list index to it.

Specify foreign key reference in Spring Data Rest, Update Request using HTTP PUT

I have an entity
#Entity
#Table(name="Indications")
#NamedQuery(name="Indication.findAll", query="SELECT i FROM Indication i")
public class Indication implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="IndicationID")
private int indicationID;
#Column(name="IndicationDescription")
private String indicationDescription;
#ManyToOne
#JoinColumn(name="ParentIndicationID")
private Indication indication;
}
As is evident in the entity the table has 3 columns
IndicationID which is a primary key
IndicationDescription which is a string
Indication which is a foreign key reference to a row in the same table, so it is many-to-one relationship
Since I am using Spring Data Rest I can create a new record using HTTP POST with the following body
{
"indicationDescription": "dummy1",
"indication": "/indications/1"
}
This creates a new row in the database with the content dummy1 and creates a foreign key reference to the record with id 1.
But when I try to update the foreign key reference using HTTP PUT with the following body
{
"indicationDescription": "dummy2",
"indication": "/indications/2"
}
While it updates the indicationDescription it does not actually update the foreign key reference.
I am aware that I can update the foreign key reference by using the URL http://localhost:8080/indications/7733/indication
and sending body as
http://localhost:8080/indications/2
after setting the content type to text/uri-list
My question is , Is there a way to perform the update in one request using the body similar to the one I used for creation of the record instead of making another update request using the URL - http://localhost:8080/indications/7733/indication
Thanks in advance.

How to establish foreign key relationship with a non entity table in the database using Spring data JPA?

My spring boot project uses an existing database, I have a new model entity/table in my project that must have a foreign key constraint with an existing table in the database.
I've tried to find solution online but all the answers are for the case where both the tables are present as entities in that project and using some #ManyToOne, #OneToMany annotations.
I can't define those annotations because I don't have the reference table as an entity or model in my project.
Let's say I have class like:
#Entity(name = "user")
public class User {
#Id
#GeneratedValue
private long userId;
private long departmentId;
I want to put a foreign key contraint on the departmentId column to reference to id column of the existing department table that isn't defined as a model or entity in my project.
Thanks
Just do it as normal
example
#Column(name = "department_id")
private Department departmentId;
You can later access it Department.departmentId. Hope this helps.
Try it like this
#ManyToOne
#JoinColumn(name="(column name of current entity)", referencedColumnName="(column name in target entity)")
private Department departmentId;
you can skip the referencedColumnName if the column name is same in both the entities

Spring JPA one to many

I have two entities :
#Entity
#Table(name="Registration")
public class Registration{
#Id
private UUID uuid;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name="registration", nullable = false)
private List<Payment> payment;
}
#Entity
#Table(name="Payment")
public class Payment {
#Id
private UUID uuid;
/*#ManyToOne(targetEntity = Registration.class) <-- MappingException: Repeated column in mapping for entity
private Registration registration;*/
}
This entities create two tables :
TABLE `registration` (
`uuid` binary(16) NOT NULL,
PRIMARY KEY (`uuid`))
TABLE `payment` (
`uuid` binary(16) NOT NULL,
`registration` binary(16) NOT NULL,
PRIMARY KEY (`uuid`),
CONSTRAINT `FK_jgemihcy9uethvoe3l7mx2bih` FOREIGN KEY (`registration`) REFERENCES `registration` (`uuid`))
I'm using Rest Service. I can access to
registration.payment
but not
payment.registration
why ? I need a relation oneToMany bidirectionnal ?
Yes, you need to add the payment.registration #ManyToOne relationship if you use it in your code.
Take into account that JPA allows you to map a SQL database model to an object oriented one. Once you have the mapping between your objects and your database, you always work at the object level. That's why, although you have the relationship in the database, your Payment object doesn't know anything about it unless you map it to an attribute.
Of course it applies when you are using you data model objects or performing JPQL or Criteria queries. If you use native queries you have access to the database model as it is.

Spring Security - Default Tables not created

I'am trying to integrate Spring Social on top of Spring Security in a Spring Boot application. But it seems like Spring Security is having issues creating the default tables, e.g. UserConnection, UserProfile, etc since I get these SQL errors after the connection to an oauth2 provider was successfully established:
PreparedStatementCallback; bad SQL grammar [select userId from UserConnection where providerId = ? and providerUserId = ?]; nested exception is org.h2.jdbc.JdbcSQLException: Tabelle "USERCONNECTION" nicht gefunden Table "USERCONNECTION" not found; SQL statement: select userId from UserConnection where providerId = ? and providerUserId = ? [42102-185]
This is a static SQL call in the spring provided JdbcUsersConnectionRepository. I tried to switch over to the InMemory implementation which avoids the SQL problem, but then the next one occurs:
PreparedStatementCallback; bad SQL grammar [INSERT into userProfile(userId, email, firstName, lastName, name, username) values(?,?,?,?,?,?)]; nested exception is org.h2.jdbc.JdbcSQLException: Tabelle "USERPROFILE" nicht gefunden Table "USERPROFILE" not found; SQL statement: INSERT into userProfile(userId, email, firstName, lastName, name, username) values(?,?,?,?,?,?) [42102-185]
The USERPROFILE Table is missing, too.
Before I post tons of configuration snippets, do you already know something I might have forgotten which tells spring to create these tables for me? :)
At the moment I am going with the Spring Boot standard H2 in-memory database, which works well with JpaRepositories.
Thank You! :)
Found the solution myself :)
I eventually found the very first thing wich is not handled by some fancy automagically Spring mechanism but with a plain 'schema.sql' in the src/main/resources directory.
create table UserConnection (
userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
rank int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(1024) not null,
secret varchar(255),
refreshToken varchar(255),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);
create table UserProfile (
userId varchar(255) not null,
email varchar(255),
firstName varchar(255),
lastName varchar(255),
name varchar(255),
username varchar(255),
primary key (userId));
create unique index UserProfilePK on UserProfile(userId);

Resources