#DataJpaTest ignores #Column(unique = true) and #Column(nullable = false) - java

I have an issue in testing save operation by Jpa.
I have a User object:
#Entity
#Table(name = "USERS", schema = "ecommercy")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"createdAt", "updatedAt"}, allowGetters = true)
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#ToString
public class User {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
#Column(updatable = false, nullable = false, columnDefinition = "VARCHAR(255)")
private String id;
#Column(unique = true, nullable = false)
private String email;
#Column(unique = true)
private String phoneNumber;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Column(nullable = false)
private String password;
#Column(nullable = false)
private String name;
private String lastName;
#OneToMany(mappedBy = "user")
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Address> addresses;
#OneToOne(mappedBy = "user")
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private Basket basket;
#OneToMany(mappedBy = "user")
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Order> orders;
#Builder.Default
#Column(nullable = false)
private int points = 0;
#OneToMany(mappedBy = "user")
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Comment> comments;
#Builder.Default
#Convert(converter = StringListConverter.class)
#Column(updatable = false)
private List<String> roles = List.of("USER");
#Builder.Default
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
#Column(nullable = false, updatable = false)
private Date createdAt = new Date();
#Builder.Default
#Column(nullable = false)
#Temporal(TemporalType.TIMESTAMP)
#LastModifiedDate
private Date updatedAt = new Date();
}
As you can see some of the user fields such as name or email have #Column(unique = true) or #Column(nullable = false)
When I run the application everything is okay and works perfectly.
But when I try to test these two annotations by Junit5 nothing will happen and no exception will be thrown and tests will be passed succesfully.
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserRepositoryTests {
#Autowired
private UserRepository userRepository;
#Test
void ifEmailIsNullMustThrowException() {
// Arrange
User user = User.builder()//
.name("John")//
.lastName("Doe")//
.password("password")//
.phoneNumber("123456789")//
.build();
// Act
User savedUser = userRepository.save(user);
// Assert
assertNull(savedUser);
}
#Test
void ifAUserExistWithGivenEmailMustThrowException() {
// Arrange
User user1 = User.builder()//
.name("John")//
.lastName("Doe")//
.email("johndoe#email.com")//
.password("password")//
.phoneNumber("123456789")//
.build();
User user2 = User.builder()//
.name("John")//
.lastName("Doe")//
.email("johndoe#email.com")//
.password("password")//
.phoneNumber("123456789")//
.build();
userRepository.save(user1);
// Act
User savedUser = userRepository.save(user2);
// Assert
assertNull(savedUser);
}
}

The #DataJpaTest makes your test #Transactional which means everything in the test runs in a transaction and will rollback after the test. That combined with the default flush mode of AUTO (which will flush automatically on commit) makes it that no actual SQL will be issues to the database in your test. (Enabling query logging would show this). With no SQL to the database no constraints will trigger and thus no exception.
To fix you can do 2 things.
Use saveAndFlush instead of save, as that will flush changes to the database and thus issue SQL.
Inject the TestEntityManager and after all your calls, before the asserts, call the flush method so synchronize the changes to the database and issue the SQL.
Either will work as it will trigger the flushing of the changes.

Related

How should I fix "Expected 0 arguments but found 3" error if I want to use Lombok RequiredArgsConstructor annotation?

I am using Spring, JPA, Java17, MySQL.
IDE: IntelliJ IDEA 2022.2.4
JDK: Amazon Coretto 17.0.6
I am getting an error "Expected 0 arguments but found 3". (image)
Here is my Article entity class code and I am using Lombok to remove boilerplate code. For some reason RequiredArgsConstructor annotation cannot be well managed in test class and I need to create actual constructor to be able to work on it.
#Entity
#Getter
#Setter
#RequiredArgsConstructor
#Table(name = "article", schema = "chitchat")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "title", nullable = false, length = 150)
private String title;
#OneToOne
#JoinColumn(name = "category_id")
private Category category;
#Column(name = "comment_count", nullable = false)
private int commentCount;
#Column(name = "view_count", nullable = false)
private int viewCount;
#ToString.Exclude
#OneToMany(mappedBy = "article", orphanRemoval = true)
private Set<Tag> tags = new LinkedHashSet<>();
#Column(name = "modification_date")
private LocalDateTime modificationDate;
#Column(name = "creation_date", nullable = false)
private LocalDateTime creationDate;
#Column(name = "content", nullable = false, length = 50000)
private String content;
#OneToOne(optional = false, orphanRemoval = true)
#JoinColumn(name = "author_id", nullable = false)
private User author;
#Column(name = "published", nullable = false)
private Boolean published = false;
#OneToMany(mappedBy = "article")
private Set<Comment> comments = new LinkedHashSet<>();
}
I tried using AllArgsConstructor and creating constructor by hand (works fine).
As docs of #RequiredArgsConstructor suggests:
Generates a constructor with required arguments. Required arguments
are final fields and fields with constraints such as #NonNull.
So, your class does not have fields that match those criteria
Whereas #AllArgsConstructor :
Generates an all-args constructor. An all-args constructor requires
one argument for every field in the class.
So, everything works as expected.

Spring Boot findById is not working but findAllById works fine

I have an Bid entity defined as follows
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Setter
#Getter
#Entity
#Table(name = "bid_details")
public class Bid {
private enum STATUS { INITIATED, DRAFT, COMPLETED }
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
private Integer id;
#Column(name = "govt_bid_id", nullable = false)
private String govtBidNumber;
#Temporal(TemporalType.DATE)
#Column(name = "release_date", nullable = false)
#JsonFormat(pattern = "dd-MM-yyyy")
private Date releaseDate;
#ManyToOne(optional = false)
#JoinColumn(name = "created_by", referencedColumnName = "id", updatable = false, nullable = false)
private User createdBy;
#Temporal(TemporalType.DATE)
#Column(name = "created_date", nullable = false)
#CreationTimestamp
private Date createdDate;
#ManyToOne
#JoinColumn(name = "updated_by", referencedColumnName = "id", updatable = false, nullable = false)
private User updatedBy;
#Enumerated(EnumType.STRING)
#Column(name = "status", nullable = false)
private STATUS status;
#Column(name = "avg_turnover")
private String avgTurnover;
#Convert(converter = StringListConverter.class)
#Column(name = "docs_required", columnDefinition = "json")
private List<String> docsRequired;
#Enumerated(EnumType.STRING)
#Column(name = "status", nullable = false)
private STATUS status;
}
and the corresponding columns are present in the bid_details tables. I have bid repository defined as follows:
public interface BidRepository extends JpaRepository<Bid, Integer> {
}
now when I try to access data by id using findById it is throwing No Value Present exception whereas if I try to access the data using findAllById I am getting correct result. Not able to figure out what's causing this weird behaviour.
Also, if I execute findAll first and then findById it is giving the correct result.
I am using spring-boot version 2.1.1
following is code where the entity is saved in the db
public Bid addBid(BidRequest bidRequest) {
User user = userRepository.findById(bidRequest.getCreatedBy()).get();
Bid bid = new Bid();
modelMapper.map(bidRequest, bid);
bid.setCreatedBy(user);
return bidRepository.save(bid);
}
BidRequest class is as follows:
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Setter
#Getter
public class BidRequest {
private String govtBidNumber;
#Temporal(TemporalType.DATE)
#JsonFormat(pattern = "yyyy-MM-dd")
private Date releaseDate;
#Temporal(TemporalType.DATE)
private Date endDate;
private int createdBy;
#Temporal(TemporalType.DATE)
#JsonFormat(pattern = "yyyy-MM-dd")
private Date createdDate;
private int updatedBy;
private String status;
private List<String> docsRequired;
}
Have you tried orElse like this
findById(id).orElse(null);
Because findById returns an Optional object so you have to write orElse() after findById()

Updating and deleting from DB using JpaRepository

I have two entities which are connected with ManyToMany relation:
User.java
#Id
#Column(name = "user_id", updatable = false, nullable = false, unique = true)
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
#Column(name = "name")
private String name;
#Column(name = "product")
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinTable(name = "products_users",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "product_id")})
private Set<Product> products;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd#HH:mm:ss")
#Column(name = "created_on")
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Date createdOn;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd#HH:mm:ss")
#Column(name = "modified_on")
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Date modifiedOn;
// construuctors, getter, setter
}
Product .java
#Id
#Column(name = "product_id", updatable = false, nullable = false, unique = true)
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
#Column(name = "name")
private String name;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd#HH:mm:ss")
#Column(name = "created_on")
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Date createdOn;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd#HH:mm:ss")
#Column(name = "modified_on")
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Date modifiedOn;
//getters, setters, contructors
}
I want to delete data from DB by users UUID. How i write query like this
#Transactional
#Modifying
#Query(value = "delete from users where user_id = ?1", nativeQuery = true)
void deleteByUUID(UUID uuid);
but it deletes the only row in the users table. I want all the data about this user and his products to be deleted.
And also I don't know how to perform an update of user and it's products correctly.
You have all the right set up regarding the cascading:
The problem is that you are triggering a native query. This bypasses all the JPA configuration and cascading altogether. Even if you used a JPQL delete without native, this would still omit all the cascading and JPA config.
Somewhere in your code, you need to fetch the User, using findOne for example. After that use the delete method. Only then the cascading will work.
Based on Maciej Kowalski answer
To find by UUID on repository:
Optional<User> findByUUID(UUID uuid);
On service to delete:
Optional<User> optionalUser = repository.findByUUID(uuid);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
repository.delete(user);
}
// here on else you can throw an Exception
On service to update:
Optional<User> optionalUser = repository.findByUUID(uuid);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
// Make changes here for example user.setName(otherName)
repository.save(user);
}
// here on else you can throw an Exception

Spring boot + jpa +mysql error on aws or openshift

I tried to deploy my application on AWS and Openshift, the applications seems to work fine until I try to access GET /users endpoint after a POST (saved user without company data).
It returns:
There was an unexpected error (type=Internal Server Error, status=500).
could not prepare statement; SQL [select companies0_.users_id as users_id1_8_0_, companies0_.companies_id as companie2_8_0_, company1_.id as id1_0_1_, company1_.contact_id as contact_4_0_1_, company1_.gallery_id as gallery_5_0_1_, company1_.active as active2_0_1_, company1_.promoted as promoted3_0_1_, company1_.location_id as location6_0_1_, company1_.profile_id as profile_7_0_1_, company1_.subscription_id as subscrip8_0_1_, contact2_.id as id1_1_2_, contact2_.email as email2_1_2_, contact2_.phone as phone3_1_2_, gallery3_.id as id1_2_3_, location4_.id as id1_3_4_, location4_.address as address2_3_4_, location4_.city as city3_3_4_, location4_.country as country4_3_4_, location4_.country_code as country_5_3_4_, location4_.latitude as latitude6_3_4_, location4_.longitude as longitud7_3_4_, profile5_.id as id1_5_5_, profile5_.company_category as company_2_5_5_, profile5_.creation_date as creation3_5_5_, profile5_.description as descript4_5_5_, profile5_.name as name5_5_5_, profile5_.thumbnail as thumbnai6_5_5_, subscripti6_.id as id1_6_6_, subscripti6_.subscription_end_date as subscrip2_6_6_, subscripti6_.subscription_start_date as subscrip3_6_6_ from users_companies companies0_ inner join companies company1_ on companies0_.companies_id=company1_.id left outer join contacts contact2_ on company1_.contact_id=contact2_.id left outer join gallery gallery3_ on company1_.gallery_id=gallery3_.id left outer join locations location4_ on company1_.location_id=location4_.id left outer join profiles profile5_ on company1_.profile_id=profile5_.id left outer join subscription subscripti6_ on company1_.subscription_id=subscripti6_.id where companies0_.users_id=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
The logs are as follows:
org.springframework.dao.InvalidDataAccessResourceUsageException:could not prepare statement
Caused by: java.sql.SQLSyntaxErrorException:user lacks privilege or object not found:COMPANIES in statement
Caused by: org.hsqldb.HsqlException:user lacks privilege or object not found:COMPANIES
But all these works perfectly fine on local server with mysql.
The controller:
#RestController
public class UserController {
#Autowired
UserRepository repository;
#GetMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity getUsers() {
return new ResponseEntity<>(repository.findAll(), HttpStatus.OK);
}
}
Repository:
#Repository
public interface UserRepository extends JpaRepository<User, String> {
}
User model:
#Entity
#Table(name = "users")
#DynamicUpdate
#EntityListeners(AuditingEntityListener.class)
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false)
#SerializedName("id")
private long id;
#Column(name = "name")
#SerializedName("name")
private String name;
#Column(name = "email", unique = true, updatable = false)
#SerializedName("email")
private String email;
#Column(name = "password")
#SerializedName("password")
#JsonIgnore
private String password;
#Column(name = "accessToken")
#SerializedName("accessToken")
private String accessToken;
#Column(name = "phoneNumber")
#SerializedName("phoneNumber")
private String phoneNumber;
#Column(name = "createdAt", nullable = false, updatable = false)
#SerializedName("createdAt")
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
private Date createdAt;
#Column(name = "updatedAt", nullable = false)
#SerializedName("updatedAt")
#Temporal(TemporalType.TIMESTAMP)
#LastModifiedDate
private Date updatedAt;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Company> companies;
//getters & setters etc
}
Company model:
#Entity
#Table(name = "companies")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false)
#SerializedName("id")
private long id;
#OneToOne(cascade = CascadeType.ALL)
#SerializedName("profile")
private Profile profile;
#OneToOne(cascade = CascadeType.ALL)
#SerializedName("contact")
private Contact contact;
#OneToOne(cascade = CascadeType.ALL)
#SerializedName("location")
private Location location;
#OneToOne(cascade = CascadeType.ALL)
#SerializedName("gallery")
private Gallery gallery;
#OneToOne(cascade = CascadeType.ALL)
#SerializedName("subscription")
private Subscription subscription;
#Column(name = "active", columnDefinition = "tinyint(1) default 0")
#SerializedName("active")
private boolean isActive;
#Column(name = "promoted", columnDefinition = "tinyint(1) default 0")
#SerializedName("promoted")
private boolean isPromoted;
//getters & setters etc
}
Application properties:
spring.datasource.url=jdbc:mysql://host:3306/database
spring.datasource.username=user
spring.datasource.password=password
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
Any idea what cause this problem? Also I mentioned above this works perfectly fine on local machine.
UPDATE Its seems if I don't provide data for every object in relations when saving (exp.User/Company,Company/Profile,Company/Contact etc) a user to db it gives this error.

How to convert a SQL query to Spring JPA query

I have a SQL query like this:
"Select UIProfileID from UserTable where UPPER(UserID) = UPPER('?1')".
I want to convert it to Spring JPA.
I want to write getUIProfileId() and return Integer. But I don't know how to implement. Because User table doesn't have UIProfileId column that it was joined from UIProfileTable table. Please help me solve it.
Currently, I have tables:
User.java
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(name = "UserTable")
public class User {
#Column(name = "UserID", length = 32, nullable = false)
#Id
private String name;
#ManyToOne
#JoinColumn(name = "DomainID", nullable = false)
private Domain domain;
#Column(name = "Password", length = 32, nullable = false)
private String password;
#ManyToOne
#JoinColumn(name = "UIProfileID", nullable = false)
private UIProfile uiProfile;
#Column(name = "ResPerpage", nullable = false)
private Integer resperpage;
#Column(name = "DefaultTab")
private Integer defaulttab;
#ManyToOne
#JoinColumn(name = "AdminProfile")
private AdminProfiles adminProfile;
#Column(name = "LanguageId")
private Integer languageId;
}
UIProfile.java
#Entity
#Getter
#Setter
#Table(name = "UIProfileTable")
public class UIProfile implements Serializable {
#Id
#Column(name = "UIProfileID", length = 11, nullable = false)
private Integer id;
#Column(name = "UIProfileName", length = 32, nullable = false)
private String name;
#OneToMany(mappedBy = "id.uiProfile")
private List<UIProfileTopLevel> topLevels;
}
UserRepository.java
public interface UserRepository extends Repository<User, String> {
Optional<User> findOne(String name);
#Query("Select UIProfileID from User where UPPER(UserID) = UPPER('admin')")
Integer getUIProfileId();
}
You can try this:
#Query("SELECT u.uiProfile.id from User u where UPPER(u.name)=UPPER('admin')")
Integer getUIProfileId();
Here User is the domain class name and u is the reference of User. with u we will access User's field NOT the column name which are specified with #Column or #JoinColumn Ex : #JoinColumn(name = "UIProfileID", nullable = false).

Categories

Resources