FetchMode.SUBSELECT not using #OrderBy in Spring Boot 3 - java

Does anyone know how to get #OrderBy annotations working with FetchMode.SUBSELECT in Spring Boot 3?
I have a Spring Boot application that uses spring-boot-starter-parent version 3.0.2. It contains an entity that has a #OneToMany relationship that uses both FetchMode.SUBSELECT and an #OrderBy. The #OrderBy is not being used. Instead of child entities being in the order specified by the #OrderBy they appear in the order they were inserted into the database.
The failure to use the #OrderBy seems to be due to a combination of using Spring Boot 3 and FetchMode.SUBSELECT. I think this because:
If I change the FetchMode to either SELECT or JOIN then the #OrderBy is used (even when using spring-boot-starter-parent version 3.0.2).
If I revert back to spring-boot-starter-parent version 2.7.8 then the #OrderBy is used (even when using FetchMode.SUBSELECT).
Has anyone else noticed this problem when moving from Spring Boot 2 to Spring Boot 3? Is anyone aware of any workaround to get #OrderBy being used when using FetchMode.SUBSELECT with Spring Boot 3?
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
#Fetch(value = FetchMode.SUBSELECT)
#OrderBy("name ASC")
private List<Child> children = new ArrayList<>();
Example application with actual and expected results
When running the Application class listed below I get the following output (children appear in the order they were inserted):
>> 1 b
>> 2 a
>> 3 c
I expected to get the following output (children appear ordered by name):
>> 2 a
>> 1 b
>> 3 c
I do get the expected output if in Parent.java I replace FetchMode.SUBSELECT with either FetchMode.JOIN or FetchMode.SELECT.
I do get the expected output if I alter the pom.xml to use spring-boot-starter-parent version 2.7.8. (If I revert to 2.7.8 then I also need to alter Parent.java and Child.java to import from javax. rather than jakarta.)
Application.java
package com.example;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
#Bean
public CommandLineRunner demo(ParentRepository parentRepository, ChildRepository childRepository) {
return (args) -> {
Parent parent = parentRepository.save(new Parent());
childRepository.save(new Child(parent, "b"));
childRepository.save(new Child(parent, "a"));
childRepository.save(new Child(parent, "c"));
for (Child child : parentRepository.findAll().get(0).getChildren()) {
System.out.println(">> " + child.getId() + " " + child.getName());
}
};
}
}
ParentRepository.java
package com.example;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface ParentRepository extends CrudRepository<Parent, Long> {
#Override
List<Parent> findAll();
}
ChildRepository.java
package com.example;
import org.springframework.data.repository.CrudRepository;
public interface ChildRepository extends CrudRepository<Child, Long> {
}
Parent.java
package com.example;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
#Entity
public class Parent {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
#Fetch(value = FetchMode.SUBSELECT)
#OrderBy("name ASC")
private List<Child> children = new ArrayList<>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public List<Child> getChildren() {
return children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
}
Child.java
package com.example;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
#Entity
public class Child {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#ManyToOne
#JoinColumn(name = "parent_id", nullable = false)
private Parent parent;
#Column(name = "name", nullable = true)
private String name;
public Child(Parent parent, String name) {
this.parent = parent;
this.name = name;
}
public Child() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>subselect-orderby-example</name>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

Related

Making field unique leads to UnsatisfiedDependencyException - MongoDB, Spring Boot

Working with MongoDB, I decided username should be unique. Okay, I use #Indexed(unique = true) (application runs fine), but #Indexed(unique = true) isn't working. I can still add 2 users with the same username.
Source (Spring Data: Unique field in MongoDB document) tells me to put spring.data.mongodb.auto-index-creation=true in my application.properties. (also tried putting it in application.yml, gave same error)
Later I also realized the #Size annotation from the jakarta.validation-api doesn't work.
User model:
import org.bson.types.Binary;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.persistence.Id;
import javax.validation.constraints.Size;
import java.util.List;
#Document(collection = "users")
public class User {
#Id
private String id;
private Binary profilePicture;
private String bio;
#Size(max = 20)
#Indexed(unique = true)
private String username;
private String password;
private List<Integer> kweets;
private List<User> followers;
private List<User> following;
}
Repository (is just standard):
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends MongoRepository<User, String> {
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
Id's do get auto-generated though, which means #Id does work.
Where could the problem lay, at first I thought it was just #Indexed, but turns out #Size doesn't work either.
It's fixed. Idk what it was, I stashed all the changes I made and it still works. I didn't change anything and it suddenly worked...

Postman Get request returns duplicate results

When I issue a Postman Get request, it returns duplicate result. Even when I had only three records in my database, Postman returns hundreds of same record duplicated.
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.resource</groupId>
<artifactId>akademiks</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>akademiks</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Entity class:
#Entity
#Table(name = "Chemistry")
public class Chemistry {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "date")
private Date date;
#Column(name = "question_no")
private Integer questionNo;
#Column(name = "question")
private String question;
#OneToOne(mappedBy = "question",cascade = CascadeType.ALL)
private ChemistryAnswer answer = new ChemistryAnswer();
public Chemistry() {}
public Chemistry(Date date, Integer questionNo, String question) {
this.date = date;
this.questionNo = questionNo;
this.question = question;
this.answer.setDate(date);
this.answer.setQuestionNo(questionNo);
this.answer.setQuestion(this);
}//accessors
Entity class:
#Entity
#Table(name = "Chemistryanswer")
public class ChemistryAnswer {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "date")
private Date date;
#Column(name = "question_no")
private Integer questionNo;
#Column(name = "solution")
private String solution;
#Column(name = "solution_url")
private String solutionUrl;
#OneToOne
#JoinColumn(name = "question_id")
private Chemistry question;
public ChemistryAnswer() {}
public ChemistryAnswer(Integer questionNo, String solution, String solutionUrl) {
this.questionNo = questionNo;
this.solution = solution;
this.solutionUrl = solutionUrl;
}
public ChemistryAnswer(Date date, Integer questionNo) {
this.date = date;
this.questionNo = questionNo;
}
public Date getDate() {
return date;
}//accessors
jpaRepository:
#RepositoryRestResource(collectionResourceRel = "chemistry", path = "chemistry")
public interface ChemistryRepo extends JpaRepository<Chemistry, Integer> {
}
Service class:
public interface ChemistryService {
public List<Chemistry>findAll();
public void save(Chemistry chemistry);
}
Service implementation:
#Service
public class ChemistryServiceImpl implements ChemistryService {
private ChemistryRepo repo;
public ChemistryServiceImpl() {}
#Autowired
public ChemistryServiceImpl(ChemistryRepo repo) {
this.repo = repo;
}
#Override
public List<Chemistry> findAll() {
return repo.findAll();
}
#Override
public void save(Chemistry chemistry) {
Chemistry tempChemistry = new Chemistry(chemistry.getDate(),
chemistry.getQuestionNo(), chemistry.getQuestion());
ChemistryAnswer answer = tempChemistry.getAnswer();
tempChemistry.setAnswer(answer);
repo.save(tempChemistry);
}
}
RestController class:
#RestController
public class ChemistryController {
private ChemistryService service;
#Autowired
public ChemistryController(ChemistryService service) {
this.service = service;
}
#GetMapping("/chemistries")
public ResponseEntity<Object>findAll(){
return new ResponseEntity<Object>(service.findAll(), HttpStatus.OK);
}
#PostMapping("/chemistry")
public void save(#RequestBody Chemistry chemistry,
HttpServletResponse response) throws IOException {
service.save(chemistry);
response.sendRedirect("/chemistries");
}
}
Stacktrace Get request:
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.12.5.jar:2.12.5]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.12.5.jar:2.12.5]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770) ~[jackson-databind-2.12.5.jar:2.12.5]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.12.5.jar:2.12.5]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.12.5.jar:2.12.5]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770) ~[jackson-databind-2.12.5.jar:2.12.5]
The issue you are facing is quite abstract. "Postman returns hundreds of same record duplicated." does not tell us much. Still, I guess that the issue is your bi-directional relationship. Try adding #JsonManagedReference and #JsonBackReference to your bi-directional relationship in your model as follows:
#JsonManagedReference
#OneToOne(mappedBy = "question",cascade = CascadeType.ALL)
private ChemistryAnswer answer = new ChemistryAnswer();
#OneToOne
#JsonBackReference
#JoinColumn(name = "question_id")
private Chemistry question;

Cascade on bidirectional #OneToOne with #MapsId

I work on spring boot 2.1.1.RELEASE, hibernate 5.3.7.FINAL
Rules are, that user can have no phone (phone is nullable in user) but, phone can't exist without a user (user is not null in phone).
Entities:
#Entity
public class Phone {
#Id
private Long id;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(final User user) {
this.user = user;
}
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Phone phone;
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public Phone getPhone() {
return phone;
}
public void setPhone(final Phone phone) {
this.phone = phone;
}
}
Controller:
#RestController
#RequestMapping
public class UserController {
private final UserService userService;
public UserController(final UserService userService) {
this.userService = userService;
}
#GetMapping("/demo")
public void createUserAndAddPhone() {
final User user = new User();
userService.save(user);
final Phone phone = new Phone();
phone.setUser(user);
user.setPhone(phone);
userService.update(user);
}
}
Repository:
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
}
Serivce:
#Service
public class UserService {
private final UserRepository userRepository;
public UserService(final UserRepository userRepository) {
this.userRepository = userRepository;
}
#Transactional
public void save(final User user) {
userRepository.save(user);
}
#Transactional
public void update(final User user) {
userRepository.save(user);
}
}
Tables:
CREATE TABLE `phone` (
`id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
)
application yml:
spring:
datasource:
url: jdbc:mysql://localhost:3308/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL5Dialect
hibernate:
ddl-auto: validate
pom xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Call GET http://localhost:8080/demo gives me an error:
org.hibernate.id.IdentifierGenerationException: attempted to assign id
from null one-to-one property [com.example.demo.Phone.user]
When I comment out userService.save(user);, it works and generates:
insert into `user`
values ( )
-- Generated identifier: 13, using strategy: org.hibernate.id.ForeignGenerator
insert into `phone` (`id`) values (?)
-- Binding parameter [1] as [BIGINT] - [13]
but if the user is persisted and then updated, it doesn't work (raises the above exception)
Ah, in the end, this is a hibernate bug (HHH-12436).
It is reproducible in a pure hibernate application by the following use case:
Session session = sessionFactory.openSession();
Transaction tr1 = session.beginTransaction();
User user = new User();
session.persist(user);
tr1.commit();
Transaction tr2 = session.beginTransaction();
Phone newPhone = new Phone();
user.setPhone(newPhone);
newPhone.setUser(user);
session.merge(user);
tr2.commit();
session.close();
As you can see from the above link it is fixed in hibernate 5.4 branch.
P.S. I was able to reproduce the problem in the latest 5.3 version (5.3.18.Final)

Fetching part of one to many property using Spring Data Projections

I want to return a tuple of Parent.id field and List<Child.id>.
Parent:
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
#Entity
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "id")
private Long parentId;
//we actually use Set and override hashcode&equals
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
public void addChild(Child child) {
child.setParent(this);
children.add(child);
}
public void removeChild(Child child) {
child.setParent(null);
children.remove(child);
}
public Long getParentId() {
return id;
}
public List<Child> getReadOnlyChildren() {
return Collections.unmodifiableList(children);
}
}
Child:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.io.Serializable;
#Entity
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "id")
private Long childId;
#ManyToOne
#JoinColumn(name = "id")
private Parent parent;
public Long getChildId() {
return id;
}
public Parent getParent() {
return parent;
}
/**
* Only for usage in {#link Parent}
*/
void setParent(final Parent parent) {
this.parent = parent;
}
}
The Spring Data Projection:
import java.util.List;
interface IdAndChildrenIds {
Long getParentId();
List<ChildId> getChildren();
}
interface ChildId {
Long getChildId();
}
The ParentRepository this is where problems begin:
import org.springframework.data.repository.CrudRepository;
public interface ParentRepository extends CrudRepository<Parent, Long> {
IdAndChildrenIds findIdAndChildrenIdsById(Long id);
}
But that doesn't work because the property doesn't comply with JavaBean standard (getter getReadOnlyChildren instead of getChildren), so I configured ObjectMapper to recognize private fields:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
#Configuration
#EnableWebMvc
public class HibernateConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper mapper = new Jackson2ObjectMapperBuilder().build();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
converters.add(new MappingJackson2HttpMessageConverter(mapper));
}
}
Then, it still doesn't work because the property is LAZY initialized and it cannot be fetched outside a transaction (and because I wrote spring.jpa.open-in-view=false in application.properties due to that being a better practice). So, I must specify explicit join using query and also must use aliases so Spring Data recognizes the properties:
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface ParentRepository extends CrudRepository<Parent, Long> {
#Query("select " +
" c.parent.parentId as parentId, " +
" c.childId as childId" +
"from Child c inner join a.parent p " +
"where p.parentId=:id")
IdAndChildrenIds findIdAndChildrenIdsById(#Param("id") long id);
}
But this again doesn't work javax.persistence.NonUniqueResultException: result returns more than one elements because the specified select gives a list of tuples: List<{parentId, childId}>, while I want one tuple of {parentId, List<childId>}.
So, regarding this answer, I added #Value("#{target.parentId}") to Long getParentId();. But that did not have any effect in my case. I still get NonUniqueResultException.
Then, I tried changing the return value of the method from IdAndChildrenIds to IdAndChildrenIds just to see if the error goes away, even though that solution would not help. But that didn't work either:
Could not write JSON: No serializer found for class org.springframework.aop.framework.DefaultAdvisorChainFactory and no properties discovered to create BeanSerializer
As I said, field visibility is already set to ANY.
Versions:
- Spring Boot 1.5.9.RELEASE
- Spring Boot Starter Data JPA
- Spring Boot Starter Web
- Spring HATEOAS
Looking at this now, weird that I want parent id and ids of its children while knowing the parent id already.
interface ChildRepo{
#org.spring...Query(value = "select id from children where parent_id = :parentId", nativeQuery = true)
List<Long> findIdsByParentId(Long parentId);
}
#lombok.Value
class IdsDto{
Long parentId;
List<Long> childrenIds;
}
public IdsDto createTupleThing(Long parentId){
return new IdsDto(parentId, childRepo.findIdsByParentId(parentId);
}

Spring Data doesn't update with save() method

I'm currently working on an application for an university project. For this project, my team and I are using a Vaadin/Spring/Maven configuration. With this we also include a database connected with repositories.
The repositories are build the following way. First a class for objects:
import javax.persistence.*;
import java.util.Set;
#Entity
#Table (name = "component")
public class Component {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
long id;
String name;
float price;
boolean isVegetarian;
#OneToMany(mappedBy = "component", cascade = {CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
Set<InvoicePosition> Positions;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
ComponentCategory category_id;
String path;
public Component() {
}
public Component(String name, float price, boolean isVegetarian, ComponentCategory category, String path) {
this.name = name;
this.price = price;
this.isVegetarian = isVegetarian;
this.category_id = category;
this.path = path;
}
Then the additional repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ComponentRepository extends JpaRepository<Component, Long>{}
Then there is a repository for component category, in which we should be able to store a list of components:
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
#Entity
#Table(name = "componentCategory")
public class ComponentCategory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
#OneToMany(mappedBy = "category_id", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
List<Component> components;
public ComponentCategory() { }
public ComponentCategory(String name) {
this.name = name;
components = new ArrayList<>();
}
public void addComponentToCategory(Component component) {
if(!components.contains(component))
{
components.add(component);
component.category_id = this;
}
}
And then the same repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ComponentCategoryRepository extends JpaRepository<ComponentCategory, Long> {}
I am able to create new entries and they are also properly stored in the database. This is all stored in the Application.Java file to start the Vaadin application
ComponentCategory cat1 = new ComponentCategory("examp1");
Component test1 = new Component("example1", 1.25f, true, cat1, "");
Component test2 = new Component("example2", 0.89f, true, cat1, "");
cat1.addComponentToCategory(test1);
cat1.addComponentToCategory(test2);
compcatrep.saveAndFlush(comp); // compcatrep is the ComponentCategoryRepository, which is passed on to the init() function
Now I'm trying to update the entries in the database with the following logic:
Component test11 = new Component("Test", 2.15f, true, compcatrep.getOne((long)4), "");
ComponentCategory comp = compcatrep.getOne((long)4);
comp.addComponentToCategory(test11);
compcatrep.saveAndFlush(comp);
I made sure that there is an entry with the ID (long) 4. It does work and does not show me any error, when I execute the application. Also if I check the length of the stored list in the ComponentCategory after the saveAndFlush():
System.out.println(comp.getComponents().size());
System.out.println(compcatrep.getOne((long)4).getComponents().size());
I do receive different length of the lists. The second Syso does show one less then the first. I tried a lot and did not find the error. Some more information about my issue:
ApplicationProperties in target folder:
spring.datasource.url=jdbc:h2:file:./mensarioDB;DB_CLOSE_ON_EXIT=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hs-augsburg.bpap</groupId>
<artifactId>mensario</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mensario</name>
<description>mensario management</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<vaadin.version>8.1.0</vaadin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

Categories

Resources