One to many assosiation using #Onetomany #JoinColumn in spring boot? - spring-boot

I am beginner in spring boot and web development and trying to do one-to-many association mapping using Onetomany and joincolumn annotation.
In my application 2 entity classes are there User and ProductDetail and i am trying to do One-to-many association between them.I am expecting user-id as foreign key in ProductDetail table.
There is no error while running my program and when i am passing the JSON statement as input to testcase my application in postman,but when i am seeing my MySql database to see if mapping occured or not, user id is not mapped as a foreign key in ProductDetail table.
I am posting the Entity classes and other classes below.....
Please help i am really stuck please let me know what i am going wrong(I know i am missing very small thing which i am unable to figure out on my own)
User class:-
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class User {
#Id
#GeneratedValue
int id;
String name;
String gender;
String emailId;
#OneToMany(targetEntity = ProductDetail.class,cascade = CascadeType.ALL)
#JoinColumn(name="up_fk",referencedColumnName = "id")
private List<ProductDetail> userpurchase;
}
ProductDetail class:-
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class ProductDetail
{
#Id
private int productid;
private String productname;
private int cost;
}
Controller class:-
#RestController
#RequestMapping("/Ecommerce")
public class UserController
{
#Autowired
private ProductService productservice;
#Autowired
private ProductRepository productRepository;
#Autowired
private UserRepository userRepository;
#GetMapping("/products")
public List<ProductDetail> ShowProducts()
{
return productservice.getProducts();
}
#PostMapping("/PlaceOrder")
public User placeOrder(#RequestBody OrderRequest request)
{
return userRepository.save(request.getUser());
}
}
OrderRequest:- class which only contains User object from which we extract user object and it will contain list of products..
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class OrderRequest
{
private User user;
}
application.properties file:-
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ecommerceapi
spring.datasource.username=root
spring.datasource.password=deep
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto =update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

Correct name is userpurchase, not ProductDetail.
{
"user": {
"name": "deep",
"gender": "male",
"emailId": "d#gmail.com",
"userpurchase": [
{
"productid": 2,
"productname": "laptop",
"cost": 50000
},
{
"productid": 3,
"productname": "earphone",
"cost": 500
}
]
}
}

I think the structure is wrong, how do you know which order asosiated with which products. If the user cancelled an order which product should not be sent?
Your order should contain a list of products and one user.

Related

Spring Boot Bad Request while sending post request with inside class on #RequestBody

I get bad request while using a nested class as a #RequestBody using postman. Is it my json? am i missing something? or this is a wrong implementation? This is my first time using a nested class for #RequestBody.
this is my class:
#Getter
#Builder
#AllArgsConstructor
public class CreateOrder {
#NotNull
private final String recipientName;
#NotNull
private final OrderAddress address;
#NotNull
private final List<OrderItem> items;
#Getter
#Builder
#AllArgsConstructor
public static class OrderItem{
#NotNull
private final Product product;
#NotNull
private final int quantity;
}
#Getter
#Builder
#AllArgsConstructor
public static class Product{
#NotNull
private final Integer id;
}
}
This is my controller:
#RestController
#RequiredArgsConstructor
#RequestMapping("api/v1/orders")
public class OrderController {
private final OrderService orderService;
#PostMapping
public ResponseEntity<Data<CreateOrderResponse>> createOrder(#RequestBody CreateOrder createOrder){
return new ResponseEntity<>(new Data<>(orderService.createOrder(createOrder)), HttpStatus.CREATED);
}
#GetMapping("/{orderID}")
public ResponseEntity<Data<GetOrderResponse>> getOrder(#PathVariable("orderID")UUID orderID){
return new ResponseEntity<>(new Data<>(orderService.getOrder(orderID)), HttpStatus.OK);
}
}
This is my json:
{
"recipientName": "TEST",
"address": {
"street": "Street AB",
"city": "City A",
"postalCode": "30001",
"detail": "Block A"
},
"items": [
{
"product": {
"id": 1
},
"quantity": 20
}
]
}
You need default constructors for the CreateOrder Model and its inner classes. You can use the #NoArgsConstructor annotation from Lombok to avoid having to have the boilerplate in your code. However, since your all your fields are final there will be 'Variable might not have been initialized' errors, so you will need to remove the final keyword from each field.

CascadeType Merge is ignored when Persist is set

Hy all
I'm having a hard time solving the following spring jpa problem.
Let's say I have the following simple data model (two entities with a one direction relationship between the two)
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity1 extends AbstractEntity {
private String name;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity2 extends AbstractEntity {
private String name;
#ManyToOne(cascade={ALL})
private Entity1 entity1;
}
and the following plumbing to store them
public interface Entity1Dao extends JpaRepository< Entity1, Long >, JpaSpecificationExecutor< Entity1 > {
Entity1 findByName(String name);
}
public interface Entity2Dao extends JpaRepository< Entity2, Long >, JpaSpecificationExecutor< Entity2 > {
Entity2 findByName(String name);
}
#Service
public class StoreService {
#Autowired
Entity1Dao dao1;
#Autowired
Entity2Dao dao2;
#Transactional
public Entity1 saveEntity1(Entity1 e) {
return dao1.save(e);
}
#Transactional
public Entity2 saveEntity2(Entity2 e) {
return dao2.save(e);
}
public Entity1 loadEntity1ByName(String name) {
return dao1.findByName(name);
}
}
#SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
}
And the following test
#SpringBootTest
#TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)
class JpaDemoApplicationTests {
#Autowired
StoreService store;
#Test
#Order(1)
void contextLoads() {
assertThat(store).isNotNull();
}
#Test
#Order(2)
void insertEntity1() {
store.saveEntity1(new Entity1("test entity1"));
Entity1 saved = store.loadEntity1ByName("test entity1");
assertThat(saved).isNotNull().hasNoNullFieldsOrProperties();
}
#Test
#Order(4)
void insertEntity2WithNewEntity1() {
store.saveEntity2(new Entity2("with new entity1", new Entity1("new entity1")));
}
#Test
#Order(5)
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
}
the last test (i.e. insertEntity2WithExistingEntity1) fails with the following exception
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.example.jpaDemo.Entity1
If I change the CascadeType in Entity2 to MERGE, that test passes but the insertEntity2WithNewEntity1 fails with the following exception
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before
flushing : com.example.jpaDemo.Entity2.entity1 ->
com.example.jpaDemo.Entity1
I've tried multiple combination of cascading types bute it seems that as soon as PERSIST is used, the last test fails (and ALL includes PERSIST).
I would have expected that if MERGE and PERSIST are set, they would both be active but form the test it looks like MERGE is ignored when PERSIST is set.
Any clues, tips, hints at what I'm doing wrong so that both tests run???
EDIT
The tests are suppose to mimick the behaviour of a REST service endpoint reveiving and saving json reprensentation of an Entity1.
The json for the third test would be
{ name: "with new entity1", entity1: { name: "new entity1"}}
The json for the fourth would be
{ name: "with new entity1", entity1: { id: 1, version: 0, name: "test entity1"}}
JPA should persists the entity1 in the third test because it's id is null but should merge the one in the fourth test because it's id is not null.
I am however unable to do both, it's either one or the other.
EDIT 2
I've modified Entity1 slightly to have a reference to the list of Entity2 associated to it and annotated it with #OneToMany and the same cascading type as in Entity2 and it's the same behavior.
When I set the cascading type to MERGE and only Merge, I'm able to save a new entity that has a reference with an existing one but I can't save a new entity with a reference to a new one.
When I set the cascading type to PERSIST (i.e PERSIST on its own, PERSIST and MERGE or ALL), it's the oppposit; I can save a new entity with a reference to anther new entity but I can't save a new entity with a reference to an already existing one.
So it's seem that when PERSIST is set, it overrides the behavior of MERGE. That, to me, is a bug. Is it not?
I've uploaded the source to github in case you want to experiment or take a look at it yourself. https://github.com/willix71/persistVsMerge.git
You need to add #Transactional on your last test. The entity loaded is detached as there is no outer transaction, you can't persist it.
#Test
#Order(5)
#Transactional
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
I'm not sure if this is relevant anymore, but the code below works as I would expect. Removing "cascade = CascadeType.PERSIST" will fail the persist test with "object references an unsaved transient instance".
I also noticed in your github repo that you are attempting to do cascading both from parent to child and child to parent. I think this is the root cause of your issues.
Entities:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
UUID id;
#ManyToOne(cascade = CascadeType.PERSIST)
Address address;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#OneToMany(mappedBy = "address")
List<User> user;
}
Repositories:
public interface UserRepository extends JpaRepository<User, UUID> {
}
public interface AddressRepository extends JpaRepository<Address, UUID> {
}
Tests:
#DataJpaTest
#Import(DataSourceConfig.class)
class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
#Autowired
private AddressRepository addressRepository;
#Test
void testMerge() {
var address = new Address();
addressRepository.save(address);
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
#Test
void testPersist() {
var address = new Address();
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
}

spring-data-rest not lazy loading thru #RepositoryRestResource embedded objects

Given the following
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#EntityListeners(AuditingEntityListener.class)
#Audited
#Builder
public class User extends BaseEntity {
#NotNull
private String firstName;
#OneToMany(fetch = FetchType.LAZY)
#NotAudited
#JoinColumn(name = "id")
private List<UserAud> audit;
}
#Entity
#Table(name = "USER_AUD")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserAud extends BaseAudEntity {
#Column(name = "FIRST_NAME")
private String firstName;
}
and
#Repository
#RepositoryRestResource(path="users")
public interface UserRepository extends JpaRepository<User, Long> {}
I see that when I #Autowire UserRepository userRepository and use userRepository.findAll() audit field is lazy loaded.
However when I access it thru #RepositoryRestResource all child audit are loaded as well.
e.g
{
"_embedded": {
"users": [
{
"id": 1,
"firstName": "cfo"
"_embedded": {
"audit": [
{
"firstName": "cfo"
}
]
}
]
....
I could not find on whether this is possible at all thru #RepositoryRestResource or not.
Off course this eager loading affects performance of controller. I am going to have many audit records.
Ideally I would like to have LAZY loading thru controller endpoint
GET /api/users
and EAGER loading thru
GET /api/users/1/audit
I later realized that most probably serialization is triggering getAudit
I tried the following
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
Hibernate5Module module = new Hibernate5Module(); // or Hibernate4Module ... depends on hibernate version you are using
module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
messageConverters.add(new MappingJackson2HttpMessageConverter(mapper));
}
and
#Bean
public Module hibernate5Module() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
return hibernate5Module;
}
and
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(Hibernate5Module.class);
}
but none of them worked :(

Spring boot MongoDb complex query

I have been learning myself MongoDB implementation in Spring Boot.
However, I came into a problem with complex queries.
I cannot find any right solution for how to implement complex queries to MongoDB from Spring boot.
I am querying the database with MongoRepository interface implementation.
Let's say that I have three collections:
Person - 1 Person can have many Pets.
Pet - 1 Pet can have 1 PetToy and 1 Person who owns him.
PetToy - 1 PetToy can belong to 1 Pet.
POJO classes are bellow
What do I want to achieve?
I want to make a query, which would be returned me a Person, whose Pet has a Toy (PetToy) with the name "Teddy".
I could not have found a way how to do it. Furthermore, is it the best practice to even use such complex queries, or is it better to write more of little ones in MongoDB?
POJOs:
#Document
#Data
#ToString
public class Person {
#Id
private String id;
private String firstname;
private String lastname;
private int age;
#DBRef
private Pet pet;
}
#Document
#Data
#ToString
public class Pet {
#Id
private String id;
private String name;
private int age;
#DBRef
private List<PetToy> toys;
}
#Document
#Data
#ToString
public class PetToy {
#Id
private String id;
private String name;
}
I have tried to use MongoRepositories; however, I was not able to make the complex query.
How can one write such a query to a MongoDB from Spring Boot?
Thank you very much in advance.
If you can use embedded attributes, the class model should be:
#Document
#Data
#Builder
public class Person {
#Id
private String id;
private String firstName;
private String lastName;
private int age;
private List<Pet> pets;
}
#Data
#Builder
public class Pet {
private String name;
private int age;
private List<PetToy> toys;
}
#Data
#Builder
public class PetToy {
private String name;
}
The repository with the method that achieves what you want:
public interface PersonRepository extends MongoRepository<Person, String> {
List<Person> getByPetsToysName(String name);
}
The getByPetsToysName method basically navigate between Person's attributes Person->pets->toys->name. More info here.
An example
#Configuration
#EnableMongoRepositories
public class TestMongo implements CommandLineRunner {
private final PersonRepository repository;
public TestMongo(PersonRepository repository) {
this.repository = repository;
}
#Override
public void run(String... args) throws Exception {
repository.save(Person.builder()
.firstName("John")
.lastName("Doe")
.age(20)
.pets(Stream.of(Pet.builder()
.name("Ursa")
.age(1)
.toys(Stream.of(PetToy.builder()
.name("Teddy")
.build())
.collect(Collectors.toList()))
.build())
.collect(Collectors.toList()))
.build());
repository.save(Person.builder()
.firstName("Phillip")
.lastName("Larson")
.age(21)
.pets(Stream.of(Pet.builder()
.name("Bella")
.age(5)
.toys(Stream.of(PetToy.builder()
.name("Lolo")
.build())
.collect(Collectors.toList()))
.build())
.collect(Collectors.toList()))
.build());
List<Person> persons = repository.getByPetsToysName("Teddy");
System.out.println(persons.size());
List<Person> persons1 = repository.getByPetsToysName("Lolo");
System.out.println(persons1.size());
}
}
Logs:
find using query: { "pets.toys.name" : "Teddy" } fields: Document{{}} for class: class Person in collection: person
If you want more complex queries you can to take a look at the Spring Data MongoDB docs.

Spring Data Rest #EmbeddedId cannot be constructed from Post Request

I have a JPA entity Person and an entity Team. Both are joined by an entity PersonToTeam. This joining entity holds a many-to-one relation to Person and one to Team. It has a multi-column key consisting of the ids of the Person and the Team, which is represented by an #EmbeddedId. To convert the embedded id back and forth to the request id I have a converter. All this follows the suggestion on Spring Data REST #Idclass not recognized
The code looks like this:
#Entity
public class PersonToTeam {
#EmbeddedId
#Getter
#Setter
private PersonToTeamId id = new PersonToTeamId();
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "person_id", insertable=false, updatable=false)
private Person person;
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "team_id", insertable=false, updatable=false)
private Team team;
#Getter
#Setter
#Enumerated(EnumType.STRING)
private RoleInTeam role;
public enum RoleInTeam {
ADMIN, MEMBER
}
}
#EqualsAndHashCode
#Embeddable
public class PersonToTeamId implements Serializable {
private static final long serialVersionUID = -8450195271351341722L;
#Getter
#Setter
#Column(name = "person_id")
private String personId;
#Getter
#Setter
#Column(name = "team_id")
private String teamId;
}
#Component
public class PersonToTeamIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return delimiter.equals(PersonToTeam.class);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
if (id != null) {
PersonToTeamId ptid = new PersonToTeamId();
String[] idParts = id.split("-");
ptid.setPersonId(idParts[0]);
ptid.setTeamId(idParts[1]);
return ptid;
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.fromRequestId(id, entityType);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
if (id instanceof PersonToTeamId) {
PersonToTeamId ptid = (PersonToTeamId) id;
return String.format("%s-%s", ptid.getPersonId(), ptid.getTeamId());
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.toRequestId(id, entityType);
}
}
The problem with this converter is, that the fromRequestId method gets a null as id parameter, when a post request tries to create a new personToTeam association. But there is no other information about the payload of the post. So how should an id with foreign keys to the person and the team be created then? And as a more general question: What is the right approach for dealing many-to-many associations in spring data rest?
After running into the same issue I found a solution. Your code should be fine, except I return new PersonToTeamId() instead of the DefaultIdConverter if id is null in fromRequestId().
Assuming you are using JSON in your post request you have to wrap personId and teamId in an id object:
{
"id": {
"personId": "foo",
"teamId": "bar"
},
...
}
And in cases where a part of the #EmbeddedId is not a simple data type but a foreign key:
{
"id": {
"stringId": "foo",
"foreignKeyId": "http://localhost:8080/path/to/other/resource/1"
},
...
}

Resources