Unable to get table autocreated in spring module integration test - spring

I have a parameters module in my project which other modules are dependent on. I'd like to write instegration tests for this module in separate manner. This module is a spring based one and has some db related logic, though db migration scripts are not available in this module since it is something that is out of its area of resposibility. By the way in-memory H2 instance is used for testing purposes.
What I'm trying to achieve is to make spring/hibernate create DB tables based on single #Entity class present in this module, it is called ParameterEntity.
It is defined like this:
#Entity
#Table(name = "SYSTEM_PARAMETERS")
public class ParameterEntity {
#Id
#Column(name = "id")
private String id;
#Column(name = "value")
private String value;
// accessors go here...
}
In my application-test.yml file I provide the following props:
spring:
jpa:
hibernate:
ddl-auto: create
database: h2
show-sql: true
And define integration test class like this:
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties
#EntityScan("path.to.package.where.parameter.entity.resides")
#ConstextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
#ActiveProfiles("test")
#TestInstance(Lifecycle.PER_CLASS)
public class ParametersIntegrationTest {
#Autowired
private ParameterWarden warden;
#BeforeEach
public void setUp() {
warden.initialize();
}
#Configuration
#ComponentScan("base.module.package")
static class TestConfiguration {
// here comes some test beans which will be used in testing purposes only
}
}
In #BeforeEach method ParameterWarden class calls repository class which in turn make some calls to database to retrieve parameter entities from database and these calls fail because SYSTEM_PARAMETERS is missing.
Could anyone, please, let me know what am I missing here and how can I make spring or hibernate create table based on the entity present in my project. Even the place where I can debug this would be nice to know.
It seems like I need another magical thing that will trigger this functionality but I was unable to figure out what exactly I need.
Any help is really appreciated, thank you very much for your time!
p.s. I can not use #SpringBootTest annotation since this module uses only some spring features and is not a spring boot application itself. It is used as a dependecy in another spring boot applications.

Can you still use #DataJpaTest?
package com.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#DataJpaTest
#ContextConfiguration(classes = TestEntityRepository.class)
#TestPropertySource(properties = {
"spring.jpa.hibernate.ddl-auto=create",
"spring.datasource.platform=h2",
"spring.jpa.show-sql=true"
})
#EnableAutoConfiguration
public class IntegrationTest {
#Autowired
TestEntityRepository testEntityRepository;
#Test
void testCreateRead() {
var saved = testEntityRepository.save(new TestEntity("test"));
Assertions.assertNotNull(saved);
var read = testEntityRepository.findById(saved.getId());
Assertions.assertNotNull(read);
}
}
Where repository and entity in the com.test package are:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface TestEntityRepository extends CrudRepository<TestEntity, Long> { }
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Data
#NoArgsConstructor
public class TestEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private Long id;
#Column
private String attr;
public TestEntity(String attr) {
this.attr = attr;
}
}
Dependecies used (through dependency management and Spring Boot BOM of version 2.3.4.RELEASE):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
Alternatively, if you want to fully decouple tests from Spring you can use org.hibernate.tool.hbm2ddl.SchemaExport in some utility logic since that's what's really executing under the hood. I use this approach since my project requires some extra steps to setup the database, however, it might be too complicated for some use cases.

Related

Spring Data Rest - validate Bean before save

I've been searching for the simplest and best way to validate my entities before they are created/updated and after much googling, I couldn't find a clean/modern one.
Ideally, I would have loved to be able to use #Valid as follows:
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Slf4j
#Validated
#Component
#RepositoryEventHandler
public class CustomerEventHandler {
// Triggered for POST
#HandleBeforeCreate
public void onBeforeCreate(#Valid Customer entity) {
log.info("Saving new entity {}", entity);
}
// Triggered for PUT / PATCH
#HandleBeforeSave
public void onBeforeSave(#Valid Customer entity) {
log.info("Saving new entity {}", entity);
}
}
The Customer entity being:
import javax.validation.constraints.NotBlank;
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "customer")
public class Customer {
#NotBlank
private String firstname;
}
But it doesn't seem to work.
What's the modern, easy way to validate entities in Spring Data REST?
Note: I'm using Spring Boot
I checked your pom.xml in linked GitHub project. You have just a dependency to validation annotations, but the proper way with Spring Boot is to use the spring-boot-starter-validation Dependency. The Spring Boot Starter Dependencies add the "magic" to your project, which triggers automatically the validation based on your annotations.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I my German blog I have written an article about this topic:
https://agile-coding.blogspot.com/2020/11/validation-with-spring.html
I want to suggest a few best practise that every developer, who starting as junior/beginner should be know. Don't put any Validation annotation in Entities, using them in DTO/Resource classes. and the best way to practise validation is that you can handler MethodArgumentNotValidation exception in your own Spring Boot of Exception Handler class annotated that class #RestControllerAdvice and create your own #interface annotation instead of using more validation annotation.

Spring Boot and Couchbase Connection Error

I am starting to learn spring boot and couchbase together, and implementing a simple custom query. However when I hit localhost:8889/agents/findByAgentId/14045, I got "this site can't be reached" error. What I missed here? I will appreciate any response. Thank you
here is the Entity Class
import com.sun.istack.internal.NotNull;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
#Document
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Agent {
#Id
#NotNull
#Field
private String AgentLeaderId;
#Field
private String agentPreference;
#Field
private String agency;
#Field
private String mobilePhone;
}
Here is the Configuration Class
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
#Configuration
public class AgentConfig extends AbstractCouchbaseConfiguration {
#Override
public String getConnectionString() {
return ("127.0.0.1");
}
#Override
public String getUserName() {
return "******";
}
#Override
public String getPassword() {
return "*******";
}
#Override
public String getBucketName() {
return "******";
}
}
Here is the Repository
import com.bit.pruleads.entity.Agent;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
import java.util.List;
#Repository
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "primaryLeadsData")
public interface AgentRepository extends CouchbaseRepository<Agent, String > {
#Query("#{#n1ql.selectEntity} WHERE agentLeaderId = $1 AND #{#n1ql.filter}")
List<Agent> findByAgentId(String agentId);
#Query("#{#n1ql.selectEntity} WHERE mobilePhone = $1 AND #{#n1ql.filter}")
List<Agent> findAgentsByPhoneNumber(String phoneNumber);
}
Controller Class
import com.bit.pruleads.entity.Agent;
import com.bit.pruleads.repository.AgentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
public class AgentController {
#Autowired
private AgentRepository agentRepository;
public AgentController(AgentRepository agentRepository) {
this.agentRepository = agentRepository;
}
#PostMapping("/findByAgentId/{id}")
public List<Agent> findByAgentId(#PathVariable String id) {
return agentRepository.findByAgentId(id);
}
#PostMapping("/findAgentsByPhoneNumber/{phoneNumber}")
public List<Agent> findAgentByPhoneNumber(#PathVariable String phoneNumber) {
return agentRepository.findAgentsByPhoneNumber(phoneNumber);
}
}
Here is the pom file
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-couchbase</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Here is the Application properties
#Server port
server.port =8889
Here is some of the Logs
No active profile set, falling back to default profiles: default
Bootstrapping Spring Data Couchbase repositories in DEFAULT mode.
Finished Spring Data repository scanning in 456ms.
Found 0 Couchbase
repository interfaces.
Bootstrapping Spring Data Couchbase repositories in DEFAULT mode.
Finished Spring Data repository scanning in 35ms.
Found 1 Couchbase repository interfaces.
Opened bucket "leads-data"
Started PruleadsApplication in 7.502 seconds (JVM running for 8.685)
Closed bucket "leads-data"
Node disconnected
Completed shutdown and closed all open buckets
Process finished with exit code 0
I think you need to have spring-boot-starter-web included in your pom. With no web server, spring-boot starts and finds it has nothing to do so it will just stop.
First of all, use this version instead:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>4.1.1</version>
</dependency>
You will need to change a few imports, so check the documentation here: https://docs.spring.io/spring-data/couchbase/docs/current/reference/html/#reference
Remove the annotations #N1qlPrimaryIndexed and #ViewIndexed, they have been replacing by new annotations: https://docs.spring.io/spring-data/couchbase/docs/current/reference/html/#couchbase.repository.indexing
You don't need to create the indexes right now, just make sure that you have a primary index on your database and you should be able to get something up and running:
CREATE PRIMARY INDEX ON MY_BUCKET
Here is an example:
https://github.com/deniswsrosa/spring-data-couchbase-quickstart
Just Run the class "QuickStartCouchbase" and after the application startup, the method "run" inside "CmdRunner" should be called.

CrudRepository class is not available int my spring boot project how can i resolve this problem

package com.example.repo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.dto.Student;
#Repository
public class StudentRepository extends CrudRepository<Student, Integer> {
}
If you want a Repository managed by Spring when using the spring-boot-starter-data-jpa, also called InstantRepository you need to create an interface, not a class. You don't need the #Repository annotation either.
package com.example.repo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.dto.Student;
public interface StudentRepository extends CrudRepository<Student, Integer> {}
Make sure com.example.repo is a sub-package of your #SpringBootApplication class.
CrudRepository is a part of spring-boot-starter-data-jpa.
So add it as a dependency to your build. For maven build it'll be:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Appending custom conditions on spring data jpa repository method queries

Short Version
I am looking for a way to have all the findBy methods of a repository class appended with a particular condition
Full Version
Let's assume I have a Product entity and Customer entity. Both of them extends the OwnerAwareEntity and they inherit ownerRef field which identifies the owner of the entity( It could be a merchant or a partner ). I want to have the findBy methods of the Product and Customer modified in runtime such that they are appended with an additional condition of the ownerRef. The ownerRef value could be identified from the user session.
Example
The parent entity class that provides the common ownerRef field
public class OwnerAwareEntity implements Serializable {
private String ownerRef;
}
Customer entity extending OwnerAwareEntity
public class Customer extends OwnerAwareEntity {
private String firstname;
private String mobile ;
}
Product entity extending OwnerAwareEntity
public class Product extends OwnerAwareEntity {
private String code;
private String name;
}
Repository class for Product & Customer extending an OwnerAwareRepository
public interface OwnerAwareRepository extends JpaRepository {
}
public interface ProductRepository extends OwnerAwareRepository {
Product findByCode(String code );
}
public interface CustomerRepository extends OwnerAwareRepository {
Customer findByFirstname(String firstname );
}
This, when executed, should result in a query like below
select P from Product P where P.code=?1 and P.ownerRef='aValue'
&
select C from Customer C where C.firstname=?1 and C.ownerRef='aValue'
What should be my approach to have this appending of condition achieved?. I only want this appending to be happening when the parent repository is OwnerAwareRepository.
TL;DR: I used #Filter of Hibernate and then created an Aspect to intercept the methods
Defined a base class entity with the following structure
OwnerAwareEntity.java
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
#MappedSuperclass
#FilterDef(name = "ownerFilter", parameters = {#ParamDef(name = "ownerRef", type = "long")})
#Filter(name = "ownerFilter", condition = "OWNER_REF = :ownerRef")
public class OwnerAwareEntity implements Serializable{
#Column(name = "OWNER_REF",nullable = true)
private Long ownerRef;
}
We set the filter on this entity. The hibernate #Filter allows us to set a condition to be appended to the select where clause.
Next, defined a base repository for the entity of type OwnerAwareEntity
OwnerAwareRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
#NoRepositoryBean
public interface OwnerAwareRepository<T, ID extends java.io.Serializable> extends JpaRepository<T, ID> {
}
Created an Aspect that will intercept all the methods from the repositories that extend OwnerAwareRepository
OwnerFilterAdvisor.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#Aspect
#Component
#Slf4j
public class OwnerFilterAdvisor {
#PersistenceContext
private EntityManager entityManager;
#Pointcut("execution(public * com.xyz.app.repository.OwnerAwareRepository+.*(..))")
protected void ownerAwareRepositoryMethod(){
}
#Around(value = "ownerAwareRepositoryMethod()")
public Object enableOwnerFilter(ProceedingJoinPoint joinPoint) throws Throwable{
// Variable holding the session
Session session = null;
try {
// Get the Session from the entityManager in current persistence context
session = entityManager.unwrap(Session.class);
// Enable the filter
Filter filter = session.enableFilter("ownerFilter");
// Set the parameter from the session
filter.setParameter("ownerRef", getSessionOwnerRef());
} catch (Exception ex) {
// Log the error
log.error("Error enabling ownerFilter : Reason -" +ex.getMessage());
}
// Proceed with the joint point
Object obj = joinPoint.proceed();
// If session was available
if ( session != null ) {
// Disable the filter
session.disableFilter("ownerFilter");
}
// Return
return obj;
}
private Long getSessionOwnerRef() {
// Logic to return the ownerRef from current session
}
}
The advisor is set to intercept all the methods from classes that extends the OwnerAwareRepository. On the interception, the current hibernate Session is obtained from entityManager ( of current persistence context ) and the filter is enabled with the param value of "ownerRef".
Also have a configuration file created to have the advisor scanned
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = {"com.xyz.app.advisors"})
public class AOPConfig {
}
Once these files are in place, you need to have the following things done for the entities that need to be owner aware
The entity needs to extend OwnerAwareEntity
The entity repository class need to extend OwnerAwareRepository
Dependencies
This setup requires the spring aop to be in the dependencies. You may add the following to the pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Advantages
Works with all select queries ( findBy methods, findAll etc )
#Query methods also gets intercepted
Simple implementation
Caveats
The where clause of delete or update is not affected by
this filter.
If the repository contains a save/update/delete method and if the
method is not tagged as #Transactional, then interceptor will give
error ( You can catch and have the method proceed normally in these
cases)
You can use Predicate of QueryDSL (or Specification) in Spring Data JPA methods.
Example:
interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
}
Predicate predicate = QUser.user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
To work with QueryDSL add to you pom.xml:
<dependencies>
//..
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>4.1.4</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.1.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Then compile your project and you will get Q-classes of your entities.
More info is here.

Test Suite Run Spring Boot Once

I am trying to create a test suite that runs Spring Boot once at the start of the suite. I have it working such that each test case has #SpringBootTest but I'd like to have #SpringBootTest in the test suite only.
I did see this but that didn't mentioned #RunWith Suite.class.
If I understood your question, for you launch many tests with spring boot you can do something like this:
1) First create yours tests classes. Here I have the first test class:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace=Replace.NONE)
public class ExampleRepositoryTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
private CustomerRepository repository;
#Test
public void testExample() throws Exception {
this.entityManager.persist(new Customer("sboot", "1234"));
Customer user = repository.findByFirstName("sboot").get(0);
assertThat(user.getFirstName()).isEqualTo("sboot");
}
}
2) My second test class.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace=Replace.NONE)
public class ExampleRepositoryTests2 {
#Autowired
private TestEntityManager entityManager;
#Autowired
private CustomerRepository repository;
#Test
public void testExample() throws Exception {
this.entityManager.persist(new Customer("sboot", "1234"));
Customer user = repository.findByFirstName("sboot").get(0);
assertThat(user.getFirstName()).isEqualTo("sboot");
}
}
3) Now let's create the suite test class:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
#RunWith(Suite.class)
#Suite.SuiteClasses({
ExampleRepositoryTests.class, //test case 1
ExampleRepositoryTests2.class //test case 2
})
public class AppTest {
}
You can start each test separately, but, if you start the suite test, the class will start every tests declared in #Suite.SuiteClasses.
These tests I am using just Spring JPA and Spring Boot. It is importante you have the dependencies in your project. Below you can see my maven dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Note that I am testing JPA Data Classes (#DataJpaTest). For others test types you will using others Spring annotations. You can see some documentation about this here.
I hope help you! o/

Resources