Spring Boot partially replacing autoconfiguration - spring

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; }
}

Related

Spring boot autowiring an interface with multiple implementations

In normal Spring, when we want to autowire an interface, we define it's implementation in Spring context file.
What about Spring boot?
how can we achieve this?
currently we only autowire classes that are not interfaces.
Another part of this question is about using a class in a Junit class inside a Spring boot project.
If we want to use a CalendarUtil for example, if we autowire CalendarUtil, it will throw a null pointer exception. What can we do in this case? I just initialized using "new" for now...
Use #Qualifier annotation is used to differentiate beans of the same interface
Take look at Spring Boot documentation
Also, to inject all beans of the same interface, just autowire List of interface
(The same way in Spring / Spring Boot / SpringBootTest)
Example below:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
public interface MyService {
void doWork();
}
#Service
#Qualifier("firstService")
public static class FirstServiceImpl implements MyService {
#Override
public void doWork() {
System.out.println("firstService work");
}
}
#Service
#Qualifier("secondService")
public static class SecondServiceImpl implements MyService {
#Override
public void doWork() {
System.out.println("secondService work");
}
}
#Component
public static class FirstManager {
private final MyService myService;
#Autowired // inject FirstServiceImpl
public FirstManager(#Qualifier("firstService") MyService myService) {
this.myService = myService;
}
#PostConstruct
public void startWork() {
System.out.println("firstManager start work");
myService.doWork();
}
}
#Component
public static class SecondManager {
private final List<MyService> myServices;
#Autowired // inject MyService all implementations
public SecondManager(List<MyService> myServices) {
this.myServices = myServices;
}
#PostConstruct
public void startWork() {
System.out.println("secondManager start work");
myServices.forEach(MyService::doWork);
}
}
}
For the second part of your question, take look at this useful answers first / second
You can also make it work by giving it the name of the implementation.
Eg:
#Autowired
MyService firstService;
#Autowired
MyService secondService;
Assume that you have a GreetingService
public interface GreetingService {
void doGreetings();
}
And you have 2 implementations HelloService
#Service
#Slf4j
public class HelloService implements GreetingService{
#Override
public void doGreetings() {
log.info("Hello world!");
}
}
and HiService
#Slf4j
#Service
public class HiService implements GreetingService{
#Override
public void doGreetings() {
log.info("Hi world!");
}
}
Then you have another interface, which is BusinessService to call some business
public interface BusinessService {
void doGreetings();
}
There are some ways to do that
#1. Use #Autowired
#Component
public class BusinessServiceImpl implements BusinessService{
#Autowired
private GreetingService hiService; // Spring automatically maps the name for you, if you don't want to change it.
#Autowired
private GreetingService helloService;
#Override
public void doGreetings() {
hiService.doGreetings();
helloService.doGreetings();
}
}
In case you need to change your implementation bean name, refer to other answers, by setting the name to your bean, for example #Service("myCustomName") and applying #Qualifier("myCustomName")
#2. You can also use constructor injection
#Component
public class BusinessServiceImpl implements BusinessService {
private final GreetingService hiService;
private final GreetingService helloService;
public BusinessServiceImpl(GreetingService hiService, GreetingService helloService) {
this.hiService = hiService;
this.helloService = helloService;
}
#Override
public void doGreetings() {
hiService.doGreetings();
helloService.doGreetings();
}
}
This can be
public BusinessServiceImpl(#Qualifier("hiService") GreetingService hiService, #Qualifier("helloService") GreetingService helloService)
But I am using Spring Boot 2.6.5 and
public BusinessServiceImpl(GreetingService hiService, GreetingService helloService)
is working fine, since Spring automatically get the names for us.
#3. You can also use Map for this
#Component
#RequiredArgsConstructor
public class BusinessServiceImpl implements BusinessService {
private final Map<String, GreetingService> servicesMap; // Spring automatically get the bean name as key
#Override
public void doGreetings() {
servicesMap.get("hiService").doGreetings();
servicesMap.get("helloService").doGreetings();
}
}
List also works fine if you run all the services. But there is a case that you want to get some specific implementation, you need to define a name for it or something like that. My reference is here
For this one, I use #RequiredArgsConstructor from Lombok.
As mentioned in the comments, by using the #Qualifier annotation, you can distinguish different implementations as described in the docs.
For testing, you can use also do the same. For example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyClassTests {
#Autowired
private MyClass testClass;
#MockBean
#Qualifier("default")
private MyImplementation defaultImpl;
#Test
public void givenMultipleImpl_whenAutowiring_thenReturnDefaultImpl() {
// your test here....
}
}
There are 2 approaches when we have autowiring of an interface with multiple implementations:
Spring #Primary annotation
In short it tells to our Spring application whenever we try to autowire our interface to use that specific implementation which is marked with the #Primary annotation. It is like a default autowiring setting. It can be used only once per cluster of implementations of an interface. → #Primary Docs
Spring #Qualifier annotation
This Spring annotation is giving us more control to select the exact implementation wherever we define a reference to our interface choosing among its options. → #Qualifier Docs
For more details follow the links to their documentation.
public interface SomeInterfaces {
void send(String message);
String getType();
}
kafka-service
#Component
public class SomeInterfacesKafkaImpl implements SomeInterfaces {
private final String type = "kafka";
#Override
public void send(String message) {
System.out.println(message + "through Kafka");
}
#Override
public String getType() {
return this.type;
}
}
redis-service
#Component
public class SomeInterfacesRedisImpl implements SomeInterfaces {
private final String type = "redis";
#Override
public void send(String message) {
System.out.println(message + "through Redis");
}
#Override
public String getType() {
return this.type;
}
}
master
#Component
public class SomeInterfacesMaster {
private final Set<SomeInterfaces> someInterfaces;
public SomeInterfacesMaster(Set<SomeInterfaces> someInterfaces) {
this.someInterfaces = someInterfaces;
}
public void sendMaster(String type){
Optional<SomeInterfaces> service =
someInterfaces
.stream()
.filter(service ->
service.getType().equals(type)
)
.findFirst();
SomeInterfaces someService =
service
.orElseThrow(() -> new RuntimeException("There is not such way for sending messages."));
someService .send(" Hello. It is a letter to ....");
}
}
test
#SpringBootTest
public class MultiImplementation {
}
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTest extends MultiImplementation {
#Autowired
private SomeInterfacesMaster someInterfacesMaster;
#Test
void sendMaster() {
someInterfacesMaster.sendMaster("kafka");
}
}
Thus, according to the Open/Closed principle, we only need to add an implementation without breaking existing code.
#Component
public class SomeInterfacesRabbitImpl implements SomeInterfaces {
private final String type = "rabbit";
#Override
public void send(String message) {
System.out.println(message + "through Rabbit");
}
#Override
public String getType() {
return this.type;
}
}
test-v2
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTestV2 extends MultiImplementation {
#Autowired
private SomeInterfacesMaster someInterfacesMaster;
#Test
void sendMasterV2() {
someInterfacesMaster.sendMaster("rabbit");
}
}
If we have multiple implementations of the same interface, Spring needs to know which one it should be autowired into a class. Here is a simple example of validator for mobile number and email address of Employee:-
Employee Class:
public class Employee {
private String mobileNumber;
private String emailAddress;
...
/** Getters & Setters omitted **/
}
Interface EmployeeValidator:
public interface EmployeeValidator {
public Employee validate(Employee employee);
}
First implementation class for Mobile Number Validator:
#Component(value="EmployeeMobileValidator")
public class EmployeeMobileValidator implements EmployeeValidator {
#Override
public Employee validate(Employee employee) {
//Mobile number Validation logic goes here.
}
}
Second implementation class for Email address Validator:
#Component(value="EmployeeEmailValidator")
public class EmployeeEmailValidator implements EmployeeValidator {
#Override
public Employee validate(Employee employee) {
//Email address validation logic goes here.
}
}
We can now autowired these above validators individually into a class.
Employee Service Interface:
public interface EmployeeService {
public void handleEmployee(Employee employee);
}
Employee Service Implementation Class
#Service
public class EmployeeServiceImpl implements EmployeeService {
/** Autowire validators individually **/
#Autowired
#Qualifier("EmployeeMobileValidator") // Autowired using qualifier for mobile validator
private EmployeeValidator mobileValidator;
#Autowired
#Qualifier("EmployeeEmailValidator") // Autowired using qualifier for email valodator
private EmployeeValidator emailValidator;
#Override
public void handleEmployee(Employee employee) {
/**You can use just one instance if you need**/
employee = mobileValidator.validate(employee);
}
}

Spring JPA custom repository different packages

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.

#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.

Entity can still be found after being deleted

I'm working with Spring Data Neo4j 4 and have the following user entity
#NodeEntity
public class User{
private Long id;
private String username;
//Getter, Setter
}
Using the Neo4j GraphRepository, i first create the user in one transaction and later delete him in a second transaction.
Working with the standalone Neo4j server on localhost:7474 i get no result when running "MATCH (n) return n" but when i run the findOne(Long id) method of the GraphRepository using the id of the User i just deleted, i get the user, i just deleted returned.
Is there some kind of behavior involved i don't understand?
Regards
Urr4
Edit:
My application class
#SpringBootApplication(scanBasePackages = {/.../})
#EnableNeo4jRepositories(basePackages = {/.../})
#EnableTransactionManagement
public class MyApplication extends Neo4jConfiguration {
public static void main(String[] args) {
SpringApplication.run(TSApplication.class, args);
}
#Override
#Bean
public Neo4jServer neo4jServer() {
return new RemoteServer(/.../);
}
#Override
#Bean
public SessionFactory getSessionFactory() {
return new SessionFactory("/.../);
}
}
After Michaels comment, i've googled a bit and added the following to my Controller:
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
Afterwards it worked - Thank you all :)

Why can't i create a neo4j relationship with spring data for neo?

i'm fairly new to spring data for neo (though i have experience with neo4j itself). i tried following the 'official' guide on spring data for neo, specifically the chapter on creating relationships.
But it seems i cannot get it to work. Spring is giving me an
java.lang.IllegalStateException: This index (Index[__rel_types__,Relationship]) has been marked as deleted in this transaction
Let me stress, that i am NOT removing any nodes or relationships. These are the relevant classes of my domain model:
#NodeEntity
public class User {
#GraphId
private Long nodeid;
#Indexed(unique = true)
private String uuid;
....
}
#NodeEntity
public class Website {
#GraphId
private Long nodeid;
#Indexed(unique = true)
private String uuid;
....
}
#RelationshipEntity(type = RelTypes.REL_USER_INTERESTED_IN)
public class UserInterest {
#GraphId
private Long nodeid;
#StartNode
private User user;
#EndNode
private Website site;
...
}
And this is my basic test which i can't get to turn green ..
(note that i omitted large portions of the code, the basic setup of the spring context etc. is working fine)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#Transactional
public class BaseTest {
#Autowired
protected Neo4jTemplate template;
#Autowired
protected GraphDatabaseService graphDatabaseService;
protected Transaction tx;
#Configuration
#EnableNeo4jRepositories
static class TestConfig extends Neo4jConfiguration {
TestConfig() throws ClassNotFoundException {
setBasePackage("me.bcfh.model");
}
#Bean
GraphDatabaseService graphDatabaseService() {
return new TestGraphDatabaseFactory().newImpermanentDatabase();
}
}
public void before() {
// provide implementation if necessary
}
public void after() {
// provide implementation if necessary
}
#Before
public void setup() throws Exception {
Neo4jHelper.cleanDb(graphDatabaseService, false);
before();
}
#After
public void tearDown() throws Exception {
after();
if (tx != null) {
tx.success();
tx.close();
tx = null;
}
}
}
public class BasicGraphTest extends BaseTest {
User user;
Website website;
UserInterest interest;
#Override
public void before() {
user = new User();
website = new Website();
website = template.save(website);
user = template.save(user);
}
#Test
#Transactional
public void dbShouldContainData() throws Exception {
UserInterest interest = new UserInterest();
interest.setSite(website);
interest.setUser(user);
template.save(interest);
// some assertions
...
}
}
The IllegalStateException is being thrown when I try persisting the UserInterest instance, which I do not understand because I am not removing anything anywhere.
The ways to create a relationship mentioned in the spring guide did not work for me either, here I got the same exception ..
Can anyone spot what I'm doing wrong here?
I am using Spring Version 4.1.4.RELEASE and Spring Data For Neo Version 3.2.1.RELEASE. Neo4j has version 2.1.6
Note: I also tried copying the domain model classes from the cineasts example into my project and borrowed a few lines of the DomainTest class but this too gives me the IllegalStateException, maybe there is something wrong with my setup?
I think you are getting your IllegalStateException because you are calling cleanDb in your setup method.
You may not need to clean the database. Since your tests are makred #Transactional anything you do in your tests gets rolled back at the end of the test.
Looks like the transaction is trying to rollback and can't find the relationship it expects.

Resources