Spring JPA custom repository different packages - spring

I'm developing a spring boot application and i'm running into an issue here. I want to have separate packages for my RepositoryImpl, RepositoryCustom and Repository classes but when i separate the packages im getting this error:
Caused by:
org.springframework.data.mapping.PropertyReferenceException: No
property customMethod found for type Demo!
It only works when i put my RepositoryImpl, RepositoryCustom and Repository classes into the same package. I've tried #EnableJpaRepositories("com.example.demo.persist") but still not working.
Is there a way i can achieve this?
Here's my code:
DemoApplication.java
#SpringBootApplication
#EnableJpaRepositories("com.example.demo.persist")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
DemoController.java
#RestController
public class DemoController {
#Autowired
DemoService demoService;
#RequestMapping("/test")
public String getUnidades() {
demoService.customMethod();
return "test";
}
}
DemoRepositoryCustom.java
public interface DemoRepositoryCustom {
void customMethod();
}
DemoRepositoryImpl.java
public class DemoRepositoryImpl implements DemoRepositoryCustom {
#Override
public void customMethod() {
// do something
}
}
DemoRepositoryCustom.java
public interface DemoRepository extends JpaRepository<Demo, Long>, DemoRepositoryCustom {
}
Demo.java
#Entity
public class Demo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", columnDefinition = "bigint")
private int id;
#NotEmpty
#Column(name = "name", columnDefinition = "VARCHAR(60)", length = 60, unique = false, nullable = false)
private String name;
// ...
DemoService.java
#Service
#Transactional
public class DemoService {
#Autowired
DemoRepository demoRepository;
public void customMethod() {
demoRepository.customMethod();
}
}
application.properties
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.url=jdbc:mysql://localhost:3306/demo?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Autodetection of custom implementations work only in packages below the one declaring the Repository.
But you can make your implementation a bean with the name matching the required class name.
That would be demoRepositoryImpl in your case.
See the documentation for details.

Actually your package hierarchy is not in right order.
Make it like this :
com.example.demo.repository
com.example.demo.repository.custom
com.example.demo.repository.custom.impl
And it will work.

Related

Why is a bean created twice in test when using #PostConstruct?

I have a configuration class that uses a properties file and it works properly.
Now I want to test that code and I have to recognize that the method annotated with #PostConstruct is run twice during the test. (In debug mode I can see that the for-loop is conducted twice.)
The configuration class:
#Slf4j
#RequiredArgsConstructor
#Configuration
#ConfigurationPropertiesScan("com.foo.bar")
public class MyConfig {
private final MyProperties myProperties;
#Autowired
private GenericApplicationContext applicationContext;
#PostConstruct
void init() {
Objects.requireNonNull(myProperties, "myProperties may not be null");
for (final MyProperties.MyNestedProperty nested : myProperties.getApps()) {
log.info("xxx {} created.", nested.getName());
applicationContext.registerBean(nested.getName(), MyContributor.class, nested);
}
}
}
The used properties class:
#Slf4j
#Data
#Validated
#ConfigurationProperties(prefix = MyProperties.CONFIG_PREFIX)
public class MyProperties {
public static final String CONFIG_PREFIX = "xxx";
#Valid
#NestedConfigurationProperty
private List<MyNestedProperty> apps;
#Data
public static class MyNestedProperty {
#NotNull
#NotEmpty
private String abc;
private String xyzzy;
#NotNull
#NotEmpty
private String name;
}
}
My attempt with the test class:
#ExtendWith(SpringExtension.class)
#RequiredArgsConstructor
#ContextConfiguration(classes = MyConfigTest.MyTestConfiguration.class)
class MyConfigTest {
#MockBean
MyProperties myProperties;
ApplicationContextRunner context;
#BeforeEach
void init() {
context = new ApplicationContextRunner()
.withBean(MyProperties.class)
.withUserConfiguration(MyConfig.class)
;
}
#Test
void should_check_presence_of_myConfig() {
context.run(it -> {
assertThat(it).hasSingleBean(MyConfig.class);
});
}
// #Configuration
#SpringBootConfiguration
// #TestConfiguration
static class MyTestConfiguration {
#Bean
MyProperties myProperties() {
MyProperties myProperties = new MyProperties();
MyProperties.MyNestedProperty nested = new MyProperties.MyNestedProperty();
nested.setName("xxx");
nested.setAbc("abc");
nested.setXyz("xyz");
myProperties.setApps(List.of(nested));
return myProperties;
}
}
}
Why does this happen and how can I prevent this behaviour?

Junit test for saving data with JPA

Am trying to make a junit test to save data with JPA. Below is my entity class
#Entity
#Table(name="book")
public class test {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="BOOK_REF_ID",nullable=false)
private int bookRefId;
#Column(name="BOOK_CODE",nullable=false)
private String bookCode;
#Column(name="BOOK_NAME",nullable=false)
private String bookDescription;
public int getBookRefId() {
return bookRefId;
}
public void setBookRefId(int bookRefId) {
this.bookRefId = bookRefId;
}
public String getBookCode() {
return bookCode;
}
public void setBookCode(String bookCode) {
this.bookCode = bookCode;
}
public String getBookDescription() {
return bookDescription;
}
public void setBookDescription(String bookDescription) {
this.bookDescription = bookDescription;
}
}
Service class is
public interface BookService()
{
public Book create(Book book);
}
Repository class is
public interface BookRepository extends
JpaRepository<Book,Integer>
{ }
Service Implementation class is
public BookServiceImpli implements BookService()
{
#Resource
BookRepository repository;
#Override
public Book create(Book book) {
// TODO Auto-generated method stub
return repository.save(book);
}
}
Now my test class is
#RunWith(SpringRunner.class)
#DataJpaTest
#SpringBootTest(classes= {JPAConfig.class})
#AutoConfigureTestDatabase(replace=Replace.NONE)
#TestPropertySource(
locations = "classpath:application.properties")
public class TestBook {
#Autowired
private BookService bookService ;
#Test
public void test() {
Book book = new Book();
book.setBookCode("abc");
book.setBookDescription("safd");
bookService.create(book);
}
Application properties contains password and database details and JPAConfig contain JPA configuration details such as entity scan database details. When am trying to run the test case am getting an error like
A component required a bean of type
'com.repository.sample.BookRepository' that could not be found.
I don't have main method in it.Am new to unit testing please anyone help me to solve the issue.

#unique constraint with database support in hibernate

I have a spring project and want to enforce uniqueness in the database on a field and get the error message back to the UI.
I have read this SO answer and it makes sense so #Column(unique = true) makes the constraint on the table but doesn't enforce it.
So the question becomes how to create a #Unique annotation that checks with the database and returns a error message into BindingResult on POST handler.
An example would be great.
UPDATE
I tried the following way to make a custom validator:
The objects (note I have added #valid to get the validator messages to navigate up to BindingResult)
Person.java
#Entity
public class Person {
public Person() {}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other stuff
#UniqueNid
private BigInteger nid;
EpisodePerson.java
#Entity
public class EpisodePerson {
public EpisodePerson(){};
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#Valid
private Person person;
EpisodeViewModel (DTO)
public class EpisodeViewModel {
#Valid
private Episode episode = new Episode();
#Valid
private List<EpisodePerson> persons = new ArrayList<>();
UniqueNid.java
#Documented
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueNiaValidator.class)
public #interface UniqueNid {
String message() default "{Duplicate ID}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueNidValidator.java
public class UniqueNidValidator implements ConstraintValidator<UniqueNid, BigInteger> {
public UniqueNidValidator(){};
private PersonRepository personRepository;
#Autowired
public void setPersonRepository(PersonRepository personRepository) {this.personRepository = personRepository;}
public UniqueNidValidator(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Override
public void initialize(UniqueNid constraint) {
}
#Override
public boolean isValid(BigInteger nid, ConstraintValidatorContext context) {
return nid != null && personRepository.existsByNid(nid);
}
}
PersonRepository.java
...
Boolean existsByNid(BigInteger nid);
...
Application.java
#SpringBootApplication
#EnableAutoConfiguration(exclude = { org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class })
public class Demo3Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(WebApplicationInitializer.class);
}
public static void main(String[] args) {
SpringApplication.run(Demo3Application.class, args);
}
#Bean
public javax.validation.Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
}
When I go to submit a person I get :
Stack Trace (abbreviated)
java.lang.NullPointerException: null
at com.example.validators.UniqueNidValidator.isValid(UniqueNidValidator.java:31) ~[main/:na]
UPDATE 2
I have also tried this configuration
public class UniqueNidValidator implements ConstraintValidator<UniqueNid, BigInteger> {
public UniqueNidValidator(){};
private PersonRepository personRepository;
public UniqueNidValidator(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Override
public void initialize(UniqueNid constraint) {
}
#Override
public boolean isValid(BigInteger nid, ConstraintValidatorContext context) {
System.out.println("About to check " +nid.toString());
System.out.println("person repo " +personRepository.toString() );
return personRepository.existsByNid(nid);
}
}
which gives:
java.lang.NullPointerException: null
at com.example.validators.UniqueNiaValidator.isValid(UniqueNiaValidator.java:29) ~[main/:na]
When I try to print the repo to console.
You'll need to create a custom validation that checks the database. For the database check you can obviously use the probably already existing Spring Data Repository and it's exists() method.
A custom validation consists of an annotation to mark the fields to be checked and a class implementing the actual check.
On minor challenge is that the class needs a default constructor and doesn't really support injecting dependencies. So anything you need, you have to basically access from some static reference, including e.g. the repository. So you probably have a separate bean which puts the repository into that static reference.
Such a bean that "catches" a repository and makes it available in a static variable might look like this.
#Component
public class RepositoryCatcher{
public static MyRepository;
public RepositoryCatcher(MyRepository r){
repository = r;
}
}
From the exception you mentioned it seems that the only possible NullPointerException is when the personRepository is incorrectly injected to the validator.
Please give a try to the solution below:
Remove the following bean from your Demo3Application and let Spring Boot create the default one instead.
#Bean
public javax.validation.Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
Remove the setter for the repository from the validator but leave the dependency in the constructor as it is right now.
#Autowired
public void setPersonRepository(PersonRepository personRepository {
this.personRepository = personRepository;
}
It's not entirely true that custom validators require a default constructor as mentioned by Jens in his answer. Spring will inject dependencies based on the constructor even though a validator isn't mark as a managed component. The #Autowired annotation is also redundant.
In addition, you probably made a mistake in the condition. You should check if a person doesn't exist (Notice the ! mark in the second part).
return nid != null && !personRepository.existsByNid(nid);
I encourage you to look into a blog post which addresses your issue. Sample code is available in the GitHub repository. You can run, test it, and then compare with your solution.
This is the validator that worked and errors into BindingResult:
UniqueNidValidator.java
public class UniqueNiaValidator implements ConstraintValidator<UniqueNid, BigInteger> {
public UniqueNiaValidator(){};
#Autowired
private PersonRepository personRepository;
public UniqueNiaValidator(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Override
public void initialize(UniqueNid constraint) {
}
#Override
public boolean isValid(BigInteger nid, ConstraintValidatorContext context) {
return !personRepository.existsByNid(nid);
}
}
Note the !personRepository.existByNid(nid);
Further more the reason that the repo was blank the second time around was because it was getting called twice as outlined here
But checking for RDBMS constraint violations on Beans probably isn't a good idea anyway.

Injecting Configuration Properties is not working in my test

I'm stuck! If I skip tests and deploy to tomcat auto wiring the configuration properties file works. In my test, it fails! I'm not sure what I'm missing.
Here is my setup:
Spring Boot v 1.2.5.RELEASE
Application.yml
git:
localRepo: './powershell-status-scripts/'
remoteRepo: 'https://github.com/...'
RepositoryProperties this class has getters and setters for the properties
#Configuration
#ConfigurationProperties(locations = "classpath:application.yml", prefix = "git", ignoreUnknownFields = false)
public class RepositoryProperties {
private String localRepo;
private String remoteRepo;
public RepositoryProperties() {
}
public String getLocalRepo() {
return localRepo;
}
public void setLocalRepo(String localRepo) {
this.localRepo = localRepo;
}
public String getRemoteRepo() {
return remoteRepo;
}
public void setRemoteRepo(String remoteRepo) {
this.remoteRepo = remoteRepo;
}
}
Application.java
#EnableAutoConfiguration
#EnableConfigurationProperties
#ComponentScan(basePackages = "com.sendash.admin")
#EnableJpaRepositories("com.sendash.admin.dao.jpa")
#EnableSwagger
public class Application extends SpringBootServletInitializer {
private static final Class<Application> applicationClass = Application.class;
private static final Logger log = LoggerFactory.getLogger(applicationClass);
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
}
GitService - Autowiring the properties works on tomcat!
#Service
#EnableConfigurationProperties
public class GitService {
#Autowired
private RepositoryProperties repositoryProperties;
public void updateLocalRepository() {
...
}
GitServiceTest this class fails on init because of a NPE. Properties is null.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class)
#Profile("test")
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
public class GitServiceTest {
#Autowired
private static GitService manager;
#Autowired
private static RepositoryProperties properties;
private static final String localRepoLocation = properties.getLocalRepo();
I do realize after pasting this that #EnableConfigurationProperties is on both the Application.java and the GitService.java class. Stopping the duplication does not fix the problem.
If you want to use Spring Boot in your tests, you should configure the tests accordingly. To do that, remove the ContextConfiguration and add the following:
#SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
This should enable injecting the configuration properties.
I did change my ContextConfiguration as suggested, but my main problem was trying to autowire a static field. It was static for #BeforeClass test setup logic so I needed to move things around a bit, but I got it working. Thanks for the suggestion.

Spring Boot partially replacing autoconfiguration

I've been exploring Spring Boot (1.2.4) to create a simple RESTful repository, using H2 & Jackson.
Like others I've noticed that, by default, #Id fields aren't in the returned Json (as described in this question: While using Spring Data Rest after migrating an app to Spring Boot, I have observed that entity properties with #Id are no longer marshalled to JSON ).
So I want to tweak the configuration to include them.
BUT I'm having trouble getting it all to work.
Basic entity class:
#Entity
public class Thing {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id; //I want this to appear in the RESTful JSON
private String someInformation;
protected Thing(){};
public String getSomeInformation() { return someInformation; }
public void setSomeInformation(String someInformation) { this.someInformation = someInformation; }
}
Basic repository interface:
public interface ThingRepository extends PagingAndSortingRepository<Thing, Long> {
List<Thing> findBySomeInformation(#Param("SomeInformation") String someInformation);
}
Simple starting application:
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
And my configuration class:
#Configuration
public class RepositoryRestMvcConfig extends SpringBootRepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Thing.class);
}
}
I can see from running Spring Boot with --debug (as described at http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html ) that my config has been found but it doesn't seem to have any effect.
Jackson works on the getters/setters on a class rather than the member variables themselves. So to make sure the id is in the JSON, simply add a getter for it:
#Entity
public class Thing {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id; //I want this to appear in the RESTful JSON
private String someInformation;
protected Thing(){};
//this is what is needed
public long getId() { return id; }
public String getSomeInformation() { return someInformation; }
public void setSomeInformation(String someInformation) { this.someInformation = someInformation; }
}

Resources