Spring Rest with Angular 2 - spring

I am having problems with inserting new rows into database with my Angular 2 application.
The problem occures when i try to insert a new entry in database that has a foreign key to another table in the database. My two models defined in Spring are:
#Entity
public class A{
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(mappedBy = "A")
private List<B> b = new ArrayList<B>();
...
}
#Entity
public class B{
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
#ManyToOne
#JoinColumn(name="aId")
private A a;
...
}
Those two models in backend have their counterparts in the frontend. I added the _links part because Spring Rest api gives links instead of foreign keys:
export class A{
id: number;
b: B[];
_links:{...}
...
}
export class B{
id: number;
a: A;
_links:{...}
...
}
I created these models based on what information i get from api. For example, a get request on localhost:8080/api/b/1 gives:
{
"id" : 1,
"_links" : {
"self" : {
"href" : "http://localhost:4200/api/b/1"
},
"b" : {
"href" : "http://localhost:4200/api/b/1"
},
"a" : {
"href" : "http://localhost:4200/api/b/1/a"
}
}
}
I can easily insert new rows into table A (since it doesn't contain foreign keys) with my angular 2 service method shown bellow:
createA(a: A): Observable<A[]>{
return this.http.post(this.AUrl, a)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server Error'));
}
Similarly, service method for creating a new model B looks like:
createB(b: B): Observable<B[]>{
return this.http.post(this.BUrl, b)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server Error'));
}
My repositories in Spring are defined as:
#RepositoryRestResource(collectionResourceRel="a", path="a", itemResourceRel="a")
public interface ARepository extends JpaRepository<A, Integer>{
}
#RepositoryRestResource(collectionResourceRel="b", path="b", itemResourceRel="b"))
public interface BRepository extends JpaRepository<B, Long>{
}
My repository configuration in Spring is:
#Configuration
public class MyRestConfiguration extends RepositoryRestMvcConfiguration{
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config){
config.setBasePath("/api");
config.exposeIdsFor(A.class);
config.exposeIdsFor(B.class);
}
}
When i try to insert a new row into the table B i get the following error in spring:
javax.validation.ConstraintViolationException: Validation failed for classes [......model.B] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=a, rootBeanClass=class ........model.B, messageTemplate='{javax.validation.constraints.NotNull.message}'}
]
I would like to know what exactly is the request payload for http post request supposed to look like, in order for the application to insert a new row of B into the database. Any help is greatly appreciated.
Edit:
I have made a spring rest controler with this mapping:
#PostMapping("/api/b")
#ResponseBody
public String createServis(#RequestBody B b){
}
If I make a http post request with this payload (using curl or by some other means, but thats not the point):
{
id:1,
aId:1
}
or this one:
{
id:1,
a:{id:1}
}
or even this one:
{
id:1,
aId:1,
a:{id:1}
}
The property a of the object b remains null / is not properly initialized.

I got my answer here Spring Rest: Cannot insert rows that have #ManyToOne column
Basicaly you need to post:
{
id: 1
a: "http://localhost:4200/api/a/1"
}
to url localhost:4200/api/b

Related

How to change the formatting of the output of Hibernate HQL query

I'm developing a Spring Boot application with Spring Data JPA. I'm using a custom JPQL query to group by some field and get the count. Following is my repository method.
#Query("SELECT v.status.name, count(v) as cnt FROM Pet v GROUP BY v.status.name")
List<Object[]> countByStatus();
It's working and result is obtained as follows:
[
[
"pending",
1
],
[
"available",
4
]
]
However, I would like my Rest endpoint to respond with an output which is formatted like this
{
"pending": 1,
"available": 4
}
How can I achieve this?
Basically you want to produce a JSON where its properties ("pending", "available") are dynamic and come from the SELECT v.status.name part of the query.
Create a DTO to hold the row values:
package com.example.demo;
public class ResultDTO {
private final String key;
private final Long value;
public ResultDTO(String key, Long value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Long getValue() {
return value;
}
}
Change your query to create a new ResultDTO per row:
#Query("SELECT new com.example.demo.ResultDTO(v.status.name, count(v)) as cnt FROM Pet v GROUP BY v.status.name")
List<ResultDTO> countByStatus();
"com.example.demo" is my package, you should change it to yours.
Then from your service class or from your controller you have to convert the List<ResultDTO> to a Map<String, Long> holding all rows' keys and values.
final List<ResultDTO> repositoryResults = yourRepository.countByStatus();
final Map<String, Long> results = repositoryResults.stream().collect(Collectors.toMap(ResultDTO::getKey, ResultDTO::getValue));
Your controller should be able to transform final Map<String, Long> results to the desired JSON

How do i check if a record already exists in table in springboot JPA?

I have a table with 4 fields. And if i inserted a record that already exists i.e all field value matches with previous record in table. How do i return record only but not insert into database ?
My model look like this:
#Entity
public class QuestionDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String department;
private String year;
private String academic_year;
private String semester;
private String type;
private String subject;
private int points;
private int unit;
// getter, setter
And Controller look this:
#Autowired
public QuestionDetailsRepository qdRepository;
#PostMapping("/api/questionDetails")
public QuestionDetails addQuestion(#Valid #RequestBody QuestionDetails qDetails) {
// here i want to first check if qDetails object is already present in table .
If present i want to return that existed record instead of inserting into table.
QuestionDetails qd = qdRepository.save(qDetails); // save only new record
return qd;
}
Using postman i send data like this:
{
"department" : "IT",
"year" : "2020",
"academic_year" : "1st year",
"semester" : "first semester",
"type" : "objective",
"subject" : "JAVA",
"points" : 10,
"unit" : 5
}
Here, i am sending data that is already present in table. So, i want to check if this record already exist? If doesn't exist insert into table otherwise return that existed record.
How do i achieve that using springboot Jpa hibernate?
Implement a select method in QuestionDetailsRepository as below. Add all the criteria which make a record unique. I am using department and year but you can use all the parameters of the QuestionDetails entity.
#Query("select qd from QuestionDetails qd where qd.department = :#{#req. department} and qd.year = :#{#req.year}")
Optional<QuestionDetails> findQuestionDetails(#Param("req") QuestionDetails req);
Ensure to implement the equals() and hashCode() in QuestionDetails class as per the unique criteria.
Your pseudo-code would look like this:
Optinal<QuestionDetails> optRecord = qdRepository.findQuestionDetails(qDetails);
if(opt.isPresent()){
return opt.get();
}else{
qdRepository.save(qDetails);
}

Spring JPA bulk upserts is slow (1,000 entities took 20 seconds)

When I tried to upsert test data(1,000 entities), it took 1m 5s.
So I read many articles, and then I reduce processing time to 20 seconds.
But it's still slow to me and I believe there are more good solutions than methods that I used. Does any one have a good practice to handle that?
I'm also wondering which part makes it slow?
Persistence Context
Additional Select
Thank you!
#Entity class
This entity class is to collect to user's walk step of health data from user's phone.
The PK is userId and recorded_at (recorded_at of the PK is from request data)
#Getter
#NoArgsConstructor
#IdClass(StepId.class)
#Entity
public class StepRecord {
#Id
#ManyToOne(targetEntity = User.class, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
private User user;
#Id
private ZonedDateTime recordedAt;
#Column
private Long count;
#Builder
public StepRecord(User user, ZonedDateTime recordedAt, Long count) {
this.user = user;
this.recordedAt = recordedAt;
this.count = count;
}
}
Id class
user field in Id class(here), it's UUID type. In Entity class, user is User Entity type. It works okay, is this gonna be a problem?
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class StepId implements Serializable {
#Type(type = "uuid-char")
private UUID user;
private ZonedDateTime recordedAt;
}
Sample of Request Data
// I'll get user_id from logined user
// user_id(UUID) like 'a167d363-bfa4-48ae-8d7b-2f6fc84337f0'
[{
"count": 356,
"recorded_at": "2020-09-16T04:02:34.822Z"
},
{
"count": 3912,
"recorded_at": "2020-09-16T08:02:34.822Z"
},
{
"count": 8912,
"recorded_at": "2020-09-16T11:02:34.822Z"
},
{
"count": 9004,
"recorded_at": "2020-09-16T11:02:34.822Z" // <-- if duplicated, update
}
]
Sample of DB data
|user_id (same user here) |recorded_at |count|
|------------------------------------|-------------------|-----|
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 04:02:34|356 | <-insert
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 08:21:34|3912 | <-insert
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 11:02:34|9004 | <-update
Solution 1 : SaveAll() with Batch
application.properties
spring:
jpa:
properties:
hibernate:
jdbc.batch_size: 20
jdbc.batch_versioned_data: true
order_inserts: true
order_updates: true
generate_statistics: true
Service
public void saveBatch(User user, List<StepRecordDto.SaveRequest> requestList) {
List<StepRecord> chunk = new ArrayList<>();
for (int i = 0; i < requestList.size(); i++) {
chunk.add(requestList.get(i).toEntity(user));
if ( ((i + 1) % BATCH_SIZE) == 0 && i > 0) {
repository.saveAll(chunk);
chunk.clear();
//entityManager.flush(); // doesn't help
//entityManager.clear(); // doesn't help
}
}
if (chunk.size() > 0) {
repository.saveAll(chunk);
chunk.clear();
}
}
I read the article that says if I add '#Version' field in Entity class, but it still additional selects. and it took almost the same time (20s).
link here ⇒ https://persistencelayer.wixsite.com/springboot-hibernate/post/the-best-way-to-batch-inserts-via-saveall-iterable-s-entities
but it doesn't help me. I think I pass the PK key with data, so It always call merge().
(If I misunderstood about #Version, please tell me)
Solution 2 : Mysql Native Query (insert into~ on duplicate key update~)
I guess Insert into ~ on duplicate key update ~ in mysql native query is may faster than merge() <- select/insert
mysql native query may also select for checking duplicate key but I guess mysql engine is optimized well.
Repository
public interface StepRecordRepository extends JpaRepository<StepRecord, Long> {
#Query(value = "insert into step_record(user_id, recorded_at, count) values (:user_id, :recorded_at, :count) on duplicate key update count = :count", nativeQuery = true)
void upsertNative(#Param("user_id") String userId, #Param("recorded_at") ZonedDateTime recorded_at, #Param("count") Long count);
}
Service
public void saveNative(User user, List<StepRecordDto.SaveRequest> requestList) {
requestList.forEach(x ->
repository.upsertNative(user.getId().toString(), x.getRecordedAt(), x.getCount()));
}
Both of two method took 20s for 1,000 entities.
Answered myself, but I still wait for your opinion.
Time to upsert to use native query
1,000 entities => 0.8 seconds
10,000 entities => 2.5 ~ 4.2 seconds
This is faster than the above two methods in the question. This is because data is stored directly in DB without going through persistence context.
pros
don't additional select
don't need to consider about Persistence Context
cons
unreadable?
too raw?
How to
Service
#RequiredArgsConstructor
#Service
public class StepRecordService {
private final StepRecordRepository repository;
#Transactional
public void save(User user, List<StepRecordDto.SaveRequest> requestList) {
int chunkSize = 100;
Iterator<List<StepRecordDto.SaveRequest>> chunkList = StreamUtils.chunk(requestList.stream(), chunkSize);
chunkList.forEachRemaining(x-> repository.upsert(user, x));
}
}
chunk function in StreamUtils
public class StreamUtils {
public static <T> Iterator<List<T>> chunk(Stream<T> iterable, int chunkSize) {
AtomicInteger counter = new AtomicInteger();
return iterable.collect(Collectors.groupingBy(x -> counter.getAndIncrement() / chunkSize))
.values()
.iterator();
}
}
Repository
#RequiredArgsConstructor
public class StepRecordRepositoryImpl implements StepRecordRepositoryCustom {
private final EntityManager entityManager;
#Override
public void upsert(User user, List<StepRecordDto.SaveRequest> requestList) {
String insertSql = "INSERT INTO step_record(user_id, recorded_at, count) VALUES ";
String onDupSql = "ON DUPLICATE KEY UPDATE count = VALUES(count)";
StringBuilder paramBuilder = new StringBuilder();
for ( int i = 0; i < current.size(); i ++ ) {
if (paramBuilder.length() > 0)
paramBuilder.append(",");
paramBuilder.append("(");
paramBuilder.append(StringUtils.quote(user.getId().toString()));
paramBuilder.append(",");
paramBuilder.append(StringUtils.quote(requestList.get(i).getRecordedAt().toLocalDateTime().toString()));
paramBuilder.append(",");
paramBuilder.append(requestList.get(i).getCount());
paramBuilder.append(")");
}
Query query = entityManager.createNativeQuery(insertSql + paramBuilder + onDupSql);
query.executeUpdate();
}
}

Spring Boot : Using JPA want to get the unique value from table

I have a table
CatID | CategoryName | scatID | subCategoryName
2 User 1 x
2 User 2 y
2 User 3 z
2 User 4 a
2 User 5 b
I am able to get all the value in JSON formate using SpringBoot.
My Requirement :
I want to get the unique CategoryName attribute but in current scenario I am getting all User coming 5 times.
I am looking for solution. Please any one can help to get over using Spring Boot JPA implementation.
You can use the Query annotation in your repository interface.
For example, below snippet code return all distinct categoryName.
Declare a simple bean class:
package com.example.test;
public class CategoryNameClass {
private String CategoryName;
public CategoryNameClass(String CategoryName) {
this.CategoryName = CategoryName;
}
public String getCategoryName() {
return CategoryName;
}
public void setCategoryName(String categoryName) {
CategoryName = categoryName;
}
}
Then use the below query:
public interface ARepo extends JpaRepository<A, String> {
#Query("SELECT DISTINCT new com.example.test.CategoryNameClass(a.categoryName) FROM A a ")
List<CategoryNameClass> findAllCategoryName();
}

Spring data jpa - the best way to return object?

I have object like this:
#Entity
public class DocumentationRecord {
#Id
#GeneratedValue
private long id;
private String topic;
private boolean isParent;
#OneToMany
private List<DocumentationRecord> children;
...
}
now I would like to get only topics and ids. Is there way to get it in format like this:
[
{
id: 4234234,
topic: "fsdfsdf"
},...
]
Because even using only this query
public interface DocumentationRecordRepository extends CrudRepository<DocumentationRecord, Long> {
#Query("SELECT d.topic as topic, d.id as id FROM DocumentationRecord d")
List<DocumentationRecord> getAllTopics();
}
I was only able to get record like this:
[
[
"youngChild topic",
317
],
[
"oldChild topic",
318
],
[
"child topic",
319
],
]
I don't like array of arrays I would like to get array of object with property id and topic. What is the nicest way to achieve that?
In Spring Data JPA you can use projections:
Interface based:
public interface IdAndTopic {
Long getId();
String getTopic();
}
Class based (DTO):
#Value // Lombok annotation
public class IdAndTopic {
Long id;
String topic;
}
Then create a simple query method in your repo:
public interface DocumentationRecordRepository extends CrudRepository<DocumentationRecord, Long> {
List<IdAndTopic> findBy();
}
You can create even dynamic query method:
List<T> findBy(Class<T> type);
Then use it like this:
List<DocumentationRecord> records = findBy(DocumentationRecord.class);
List<IdAndTopic> idAndTopics = findBy(IdAndTopic.class);
You can create a class with attributes id and topic and use constructor injection into query. Sth like below
#Query("SELECT NEW your.package.SomeObject(d.id, d.topic) FROM DocumentationRecord d")

Resources