I'm testing my jpa codes with junit, which using ExampleMatcher.
This code should filter users with email by matching end of it.
Here's my code about domain object(User)
#Data
#AllArgsConstructor
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table(name = "myuser")
public class User {
#Id
#GeneratedValue
private long id;
#NonNull
private String name;
#NonNull
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
And I tested filtering with my code below.
#Test
void crud7(){
userRepository.save(new User("hyechan", "hyechan1108#google.com"));
userRepository.save(new User("steve", "steve4234#google.com"));
userRepository.save(new User("roger", "roger11#naver.com"));
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("name")
.withMatcher("email", endsWith());
Example<User> example = Example.of(new User("ma", "google.com"), matcher);
userRepository.findAll(example).forEach(System.out::println);
}
I thought this code would work fine, but no matching data was found.
I've also made sure that three user data are properly been saved.
Also, I found that Hibernate log contains code about matching primary key in query.
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.name,
u1_0.updated_at
from
myuser u1_0
where
u1_0.id=?
and u1_0.email like ? escape '\'
I don't know why that happened, but when I fix my filtering code like below, it works fine as I want.
I added code to ignore primary key (.withIgnorePaths("id"))
#Test
void crud7(){
userRepository.save(new User("hyechan", "hyechan1108#google.com"));
userRepository.save(new User("steve", "steve4234#google.com"));
userRepository.save(new User("roger", "roger11#naver.com"));
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("name")
.withIgnorePaths("id")
.withMatcher("email", endsWith());
Example<User> example = Example.of(new User("ma", "google.com"), matcher);
userRepository.findAll(example).forEach(System.out::println);
}
So, does ExampleMatcher works with primary key by default, or am I missing something?
FYI) Release Version
id 'org.springframework.boot' version '3.0.2'
com.h2database:h2:2.1.214
Related
I've got problem from real-life scenario. I am not expert on spring boot so sorry again if that's easy question. I am using Java 17, spring boot, jpa repository and postgresql driver.
The task is:
From query paramters from url-path find all entities from db by query parameters. These entities have to have a dateOfDeletion == null.
My approach is this:
#Data
#Entity
#Table(name="x", schema = "\"xx\"")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.UUID)
#Column(nullable = false)
private UUID id;
private String field1;
private String field2;
private String field3;
private Timestamp dateOfDeletion;
}
The Path is
localhost:8080/myEndpoint?field1=a&field3=b
.
I will setup a MyEntity like this for example ( this doesn't really matter ).
MyEntity queryExample = new MyEntity();
queryExample.setField1("a");
queryExample.setField3("b");
I need to find all substrings in field1 field2 and field3, so I created a customMatcher.
ExampleMatcher customMatcher = ExampleMatcher.matchingAll().
withMatcher("field1",ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
withMatcher("field2",ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
withMatcher("field3",ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
Example<MyEntity> example = Example.of(queryExample,customMatcher);
return repository.findAll(example,myPageable);
The result of this findAll will be all entities which containts in field1 string "a" and in field3 string "b".
But it will also show all entites which are not valid ( valid entity has dateOfDeletion == null )
The question is : what is the best approach? Thank you for all answers.
We can't use stream after completing the transaction because if we use findAll(example,pageable)
it would be not valid( inconsistentcy in paging )
So this solution in not valid.
repository.findAll(example).stream().filter(ent -> ent.getDateOfDeletion() == null).collect(Collectors.toList());
I have a DB with 40 columns, and a REST API in Spring Boot that can be used to query the DB with any combination of parameters, which necessitates the use of dynamic query creation, which is why I have chosen to use QueryDSL.
Before making the query, I convert the query params to the Entity object using an ObjectMapper, then do a count using something like:
queryFactory.select(qTableRequest.rowId.count()).from(qTableRequest).where(qTableRequest.eq(TableRequest))
which works fine when the #Id column of the Entity is not null, but fails with
TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
when it's null.
Side note: I can't use Spring's Query by Example because there are cases where I need non-singular matching.
I tried to extract the non-null fields of the entity and construct the query using .and(), .or() etc. as a BooleanExpression and it works, but it requires a lot of boilerplate code and it would be nice to have a simple solution to query for the Entity directly.
My question is: is it possible to use an Entity in the query without an Id?
Entity:
#Entity
#Table(name = "table", schema = "schema")
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class TableRequest {
#Id
#Column(name = "row_id")
public String rowId;
#Column(name = "order_id")
public String orderId;
... // 38 more rows
}
Query:
private BooleanExpression constructQuery(SearchRequest searchRequest) {
TableRequest searchRequestToEntity = objectMapper.convertValue(SearchRequest, TableRequest.class);
BooleanExpression queryParameters = qTableRequest.eq(searchRequestToEntity);
...
}
public SearchResponse search(SearchRequest searchRequest) {
BooleanExpression queryParameters = constructQuery(searchRequest);
long rowCount = queryFactory.select(qTableRequest.rowId.count())
.from(qTableRequest).where(queryParameters).fetchFirst();
...
}
I have an error during an update transaction on my database using JpaRepository. I saw that there are many questions about this same error here but none of the solutions mentioned seem to solve my case. This is my main method:
public View update(Long productCode, Long customerCode, UpdateForm form) {
var entityToUpdate = repository.findByProductCodeAndCustomerCode(productCode, customerCode);
var updatedObject = new Builder(entityToUpdate).build(form);
var savedObject = repository.save(updatedObject); //error thrown here
return converter.convertToView(savedObject);
}
This is the Builder class implementation that appears above:
public class Builder {
private final Purchase entityToUpdate;
public Builder(Optional<Purchase> entityToUpdate) {
this.entityToUpdate = entityToUpdate.isPresent() ? entityToUpdate.get() : null;
}
public Purchase build(PurchaseUpdateForm form) {
this.entityToUpdate.setDiscount(form.getDiscount());
this.entityToUpdate.getId().setPurchaseBoxQuantity(form.getPurchaseBoxQuantity());
return entityToUpdate;
}
}
I tried to put an #Transactional in the update() and build() methods but the error persists. I also tried using saveAndFlush but nothing changed. Full log message:
Optimistic locking failed; nested exception is caused by: org.hibernate.StaleObjectStateException:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):
[PurchasePK#bb7039e8]
# Additional Information - Domain
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_PURCHASE")
public class Purchase {
#EmbeddedId
private PurchasePK id;
#NotNull
#Column(name = "DISCOUNT")
private BigDecimal discount;
}
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(of = { "product", "purchaseBoxQuantity" })
#Embeddable
public class PurchasePK {
#NotNull
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CD_PRODUCT")
#JoinColumn(name = "CD_CUSTOMER")
private Product product;
#NotNull
#Column(name = "QT_BOXES")
private Long purchaseBoxQuantity;
}
The error you are seeing means Hibernate tried to update an entity, but the update statement actually updated 0 rows.
Hibernate assumes this is because the version attribute did not match, since for the purpose of optimistic locking all updates on entities with a version attribute have a where clause like the following.
WHERE id = ? and version = ?
The id = ? ensures that the correct row is updated. The version = ? ensures that the version hasn't changed. It does get changed with every update.
Since you don't have a version attribute in your entity, I'm somewhat surprised that you still get this error, but since you do, the only explanation is that the id does not match the one in the database.
So either the entity was deleted, or the id changed.
Make sure the id didn't get changed by accident.
Make sure the id gets passed to the database as expected. You can do that by enabling logging of SQL statements and parameters
Without having the full source, I can just guess that the problem is on your embedded id, specifically I guess it has to be with
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
I suggest you to add the following properties to your application properties file
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
so you can check how your entity's id is probably changed in a way you didn't expect to.
I am having troubles with using Postgres UUID type, java.util.UUID and Hibernate.
Everything works fine with native queries, but when I try to use HQL it tells me it could not find id column: column huser0_.id does not exist.
Here's the code for the repository:
import com.xobotun.common.HUser;
import java.util.UUID;
#org.springframework.stereotype.Repository
public interface ReadOnlyUserRepository extends Repository<HUser, UUID> {
// #Query("select u from HUser u where u.id = :id")
#Query(value = "select * from \"user\" where id = :id", nativeQuery = true)
HUser getById(#Param("id") UUID id);
}
This way it prints the expected
HUser(id=fbd3c9e2-8fa4-11e9-bc42-526af7764f64, name=test, about=test, isPermabanned=false, created=2019-06-15T19:37:30.503, lastActive=2019-06-15T19:37:33.512)
But when comment out the native query and use HQL one, it suddenly stops working:
org.postgresql.util.PSQLException: ERROR: column huser0_.id does not exist
Has anyone encountered such an issue? Thanks.
Some more info to understand the question and to check if I have any typos. :)
Table DDL:
CREATE TABLE "user" (
id UUID NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
is_permabanned BOOLEAN DEFAULT FALSE NOT NULL,
created TIMESTAMP DEFAULT now() NOT NULL,
last_active TIMESTAMP,
about TEXT
);
Entity class:
package com.xobotun.common;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
#Data
#Entity
#Table(name = "user")
public class HUser {
#Id
#Column(name = "id") //, columnDefinition = "uuid", updatable = false)
// #Type(type="pg-uuid")
private UUID id;
#Column(name = "name")
private String name;
#Column(name = "about")
private String about;
#Column(name = "is_permabanned")
private Boolean isPermabanned;
#Column(name = "created")
private LocalDateTime created;
#Column(name = "last_active")
private LocalDateTime lastActive;
}
As you can see, I experimented with various options on id field, but none of them worked with HQL query.
Java version is 11, PostgreSQL is also 11 and here are related dependencies:
dependencies {
compile 'org.springframework.data:spring-data-jpa:2.1.5.RELEASE'
compile 'javax.persistence:javax.persistence-api:2.2'
compile 'com.vladmihalcea:hibernate-types-52:2.4.4'
implementation 'org.hibernate:hibernate-core:5.4.3.Final'
implementation 'org.postgresql:postgresql:42.2.5.jre7'
}
Also, I tried solutions in these questions, but they were of no help: 1 2 3 4
UPD1:
Here's SQL generated by Hibernate on failing query:
select
huser0_.id as id1_0_,
huser0_.about as about2_0_,
huser0_.created as created3_0_,
huser0_.is_permabanned as is_perma4_0_,
huser0_.last_active as last_act5_0_,
huser0_.name as name6_0_
from
user huser0_
where
huser0_.id=?
Thanks to #JBNizet kind comment I found out the problem was not in weird UUID behaviours, but that Hibernate does not escape identifiers by default.
There are actually three easy solutions to the question:
Do not use reserved keywords, change table name to something else.
Manually escape table name (like #Table(name = "\"user\"") in HUser.java).
Add line hibernate.globally_quoted_identifiers=true to your config. I wonder why is it not true by default... See this for more details.
I have the following object structure:
#Document(collection = "user")
#TypeAlias("user")
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
and here is the Contact pojo:
public class Contact {
#Indexed(unique = true)
private String mail;
}
But for some reasons not known to me, I don't see Spring-data creating a unique index for the property info.mail
To summarize, I have this json structure of user object:
{_id:xxxxx,info:{mail:"abc#xyz.shoes"}}
And I want to create a unique index on info.mail using Spring data with the above pojo structure. Please help.
As far as I remember, annotating embedded fields with #Indexed will not work. #CompoundIndex is the way to go:
#Document(collection = "user")
#TypeAlias("user")
#CompoundIndexes({
#CompoundIndex(name = "contact_email", def = "{ 'contact.mail': 1 }", unique = true)
})
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
In my case I had a fresh spring boot application 2.3.0 with just #Document, #Id and #Indexed annotations. I was able to retrieve and insert documents but it refused to create the index other than the PK. Finally I figured that there is a property that you need to enable.
spring.data.mongodb.auto-index-creation = true
As a matter of fact it even works on nested objects without #Document annotation.
Hope this helps :)
Obsolete answer, this was with and older version of mongodb 1.x.
Had the same issue, it seems that your Contact class is missing the #Document annotation i.e.
#Document
public class Contact {
#Indexed(unique = true)
private String mail;
}
Should work, quote from the spring mongodb reference
Automatic index creation is only done for types annotated with #Document.
Extending #Xenobius's answer:
If any configuration extending AbstractMongoClientConfiguration is set, MongoMappingContext will back off. The result is:
spring.data.mongodb.auto-index-creation = true will not be effective
You will need add this into your own configuration:
#Override
protected boolean autoIndexCreation() {
return true;
}
ref: https://github.com/spring-projects/spring-boot/issues/28478#issuecomment-954627106