Spring data rest jpa post object with existing related entity - spring-data-rest

I have two entities
#Entity
public class ForwardRule {
#Column(nullable = false)
private String identifier;
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
#Column(nullable = false)
private String identifier;
}
For both I have Repositories annotated with: #RepositoryRestResource
I have an existing user and I want to post a FowardRule with an existing User.
String requestJson = "{" +
"\"identifier\": \"6bff6eb0-9b47-42fd-8ac6-339a93aa310d\"," +
"\"user\" : {\"href\":\"http://localhost:9000/users/1\"}"+
"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(requestJson, headers);
String answer = restTemplate.postForObject("http://localhost:9000/forwardRules", entity, String.class);
System.out.println(answer);
As a result I get a mysql exception: Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'user_id' cannot be null
I would like to know two things:
why is the user_id null
how can I do the post with Java objects? In following style. This works for getting and posting simple objects.
ForwardRuleView newOne = new ForwardRuleView();
newOne.setUser(existingUser);
newOne.setIdentifier("howie");
ResponseEntity forwardRuleViewResponseEntity = new RestTemplate().postForEntity("http://localhost:9000/forwardRules", newOne, ForwardRuleView.class);
Update
Json post can be done with:
String requestJson = "{" +
"\"identifier\": \"6bff6eb0-9b47-42fd-8ac6-339a93aa310d\"," +
"\"user\" : {"http://localhost:9000/users/1\"}"+
"}";
Only need to know how I can post it with Pojo's

Related

Query Many-to-many jpa

I need to implement SELECT with a many-to-many relationship in #QUERY. Perhaps I am misinterpreting the information written in the documentation.
My query looks like this:
#Query("select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1")
List<MasageEntity> findMassagesIdByStringId(#Param("strings_id") long strings_id);
In my example, I use table names. The names are underlined as an error (without compilation). Maybe I should use Entities. Then how do I do it with many-to-many relationship?
I will show a part of my Entities.
I have two Entities. MasageEntity and RstringEntity.
//MasageEntity
#Entity
#Table(name = "massages")
public class MasageEntity {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#Column(name = "string_speed")
private Long string_speed;
#Column(name = "string_color_type")
private Long string_color_type;
#Column(name = "string_color")
private String string_color;
#Column(name = "string_timing_type")
private String string_timing_type;
#Column(name = "string_timing")
private String string_timing;
#Column(name = "showed")
private Long showed;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
//RstringEntity
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name="code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MasageEntity> masagess = new HashSet<>();
public RstringsEntity() {}
There are multiple ways how to perform queries in Spring Boot: native SQL and JPQL.
In case of native queries we are using pure SQL language, defining query on DB level.
In case of Java Persistence Query Language (JPQL) we define query via entity objects.
Solution 1, native queries
You created the native query in repository, but to use it we need mark it like SQL nativeQuery = true. It required for framework to understang what query laguage do you use. #Query annotation use JPQL by defult, so that's the reason of your errors.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via native query
#Query(value = "select massages.* from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<MassageEntity> findMassagesByStringIdNativeSQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via native query
#Query(value = "select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<Long> findMassagesIdByStringIdNativeSQL(#Param("strings_id") long strings_id);
}
Solution 2, JPQL queries
Example how to define JPQL queries for your case. JPQL will be translated to SQL during execution.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via JPQL
#Query("select message from MassageEntity message join message.strings string where string.id = :strings_id")
List<MassageEntity> findMassagesByStringIdJPQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via JPQL
#Query("select message.id from MassageEntity message join message.strings string where string.id = :strings_id")
List<Long> findMassagesIDByStringIdJPQL(#Param("strings_id") long strings_id);
}
Native query generated by Hibernate:
select
massageent0_.id as id1_3_,
massageent0_.string_text as string_t2_3_
from
massages massageent0_
inner join
string_massage strings1_
on massageent0_.id=strings1_.massage_id
inner join
string rstringsen2_
on strings1_.string_id=rstringsen2_.id
where
rstringsen2_.id=?
Solution 3, Spring auto-generated queries
Spring can auto-generate queries by repository method definition.
Example for your case:
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID
List<MassageEntity> findByStrings_Id(#Param("id") long strings_id);
}
Entries which I used fro sulutions:
#Entity
#Table(name = "massages")
public class MassageEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
private Set<RstringsEntity> strings = new HashSet<>();
}
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MassageEntity> massages = new HashSet<>();
}

Spring data jpa native query. Insert object

I'm using a native query on my repository. I want to insert object data, but the query doesn't understand the object fields. How to solve this problem, I don't want to have a method with a lot of parameters.
My class:
public class StudentDTO {
private long numberzachetka;
private String fio;
private Date entrydate;
private int course;
private int numbergroup;
private long specialty;
private long faculty;
//getters, setters..}
Native query:
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
Student entity:
#Entity
#Table(name="student")
public class Student {
#Id
#Column(name="numberzachetka", nullable = false)
private long numberzachetka;
#Column(name="fiostudent", nullable = false, length = 100)
private String fio;
#Temporal(TemporalType.DATE)
#Column(name = "entrydate", nullable = false)
private Date entrydate;
#Column(name="course", nullable = false)
private int course;
#Column(name="numbergroup", nullable = false)
private int numbergroup;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "specialtykey", nullable = false)
private Specialty specialty;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "facultynumber", nullable = false)
private Faculty faculty;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student")
private Set<Performance> performances;
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
This is not the right way to save value in db using JPA repository.
If you want to save value in DB there is very simpler way, follow below steps:
Copy all the values from the studentDTO to Student object using below code.
Student student = new Student();
BeanUtils.copyProperties(studentDto,student);
create StudentRepository interface
#Repository public interface StudentRepository extends
JpaRepository<Student, Long> { }
#Autowired above Repository in the service class
#Autowired private StudentRepository studentRepository;
Save the Student object using repository predefined save method.
studentRepository.save(student);
These are the minimal change you want to save object in db, instead of going through native SQL query.
Within the entity you could have a constructor accepting the DTO. The constructor transfers the DTO attributes to the fields of the entity. Then you can just use the JpaRepository save method. You don't need the select statement. Keep in mind to add also an empty constructor which is required by JPA.
Another Idea which I use in my projects is to use mapstruct to transfert attributes from DTOs to entities.
The way you present your idea is not possible. JPA does not know how to map a DTO to an entity.

Cant get hibernate to update object without EntityExistException being thrown

I am trying to do a bidirectional one to one relation and when updating the AccountExtrasModel on the first save it works fine but when updating I get either an error or the sql statements adds an insert and then a delete instead of an update.
import lombok.*;
import javax.persistence.*;
#ToString
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "Account")
public class AccountModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Account_ID;
#Column(nullable = false, updatable = false)
private String name;
#Column(nullable = false, unique = true, updatable = false)
private String email;
#Column(nullable = false, updatable = false)
private String password;
#OneToOne(mappedBy = "accountModel", cascade = CascadeType.ALL, orphanRemoval = true)
private AccountExtrasModel accountExtras;
public AccountModel addExtras(AccountExtrasModel accountExtrasModel) {
accountExtrasModel.setAccountModel(this);
this.setAccountExtras(accountExtrasModel);
return this;
}
}
import lombok.*;
import javax.persistence.*;
#Setter
#Getter
#NoArgsConstructor
#ToString
#Entity
#Table(name = "AccountExtras")
public class AccountExtrasModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private AccountModel accountModel;
#Lob
private String description;
private String[] myVideos;
private String[] likedVideos;
private String imageReference;
}
If i change the #MapsId to #JoinColumn in AccountExtrasModel then i get the desired result but what its doing is inserting a new row and linking it to acccount and then deleting the old row instead of doing an update.
This is the error im getting:
{
"timestamp": "2018-04-26T18:19:01.657+0000",
"status": 500,
"error": "Internal Server Error",
"message": "A different object with the same identifier value was already associated with the session : [com.alttube.account.models.AccountExtrasModel#1]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.alttube.account.models.AccountExtrasModel#1]",
"path": "/update_account"
}
What can i do to get the desired result which is to simply perform an update on the accountExtrasModel to the corresponding account to which it belongs?
If you update an AccountModel instance by setting a new instance of AccountExtrasModel, as you do in addExtras(), then it is normal to have a delete+insert. By updating the id you would end with orphaned records.
When you set new values on AccountExtrasModel instance, check if accountExtras is initialized. If not, do the addExtras() stuff. If it is, do not replace it, just change it, so hibernate will generate an update on the extras table (but keep the record id unchanged).
Instead of use the method save try use the method merge, because in case of object exist in database the framework will update this object.
Case the object don't exist, the framework will insert as normal.
Regards!

JPA query to retrieve data using list of matching tuples as arguments [duplicate]

I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs
I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.
It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.

Ebean / Play Framework 2.1 issues fetching a parent/children tree structure

I encountered a problem when trying to fetch a tree structure (parent/children elements in one table).
Using the class below, starting with the root node, I can use getSubNodes() and will receive its children. However, if I then call getSubNodes() on a child node which has itself child nodes in the db, getSubNodes() will return an empty list (the db data contains multiple tree levels)
#Entity
#Table(name = "navigation_items")
public class NavigationItem extends Model {
public static NavigationItemFinder find = new NavigationItemFinder();
private static final long serialVersionUID = 1L;
#Id
private UUID id;
#Column (unique=true)
private String key;
#ManyToOne
private NavigationItem parentNode;
#OneToMany (mappedBy="parentNode", cascade=CascadeType.ALL)
private List<NavigationItem> subNodes;
#Column
#Required
private String title;
#Column
private String subtitle;
#ManyToOne
private PageBlock page;
...
public List<NavigationItem> getSubNodes() {
return subNodes; // <- This crazily but provenly only returns results on the first level of the hierarchy...
}
If I however do not return the subnodes property using the getter, but instead use a finder query, all works as expected and the full tree can be retrieved:
List<NavigationItem> result = find.where().eq("parentNode", this).findList();
Logger.debug("NavItem " + getTitle() + "[" + getId() +"]" + ": returning # subnodes: " + result.size());
Am I missing something here, or could this be an(other) ebean bug?
try this
#OneToMany(cascade = CascadeType.REFRESH, optional = false)
#JoinColumn(name = "currentTablecolumnName", referencedColumnName = "ID", nullable = false)