Let's say I have a Customer entity with a list of Vehicles:
#Document
public class Customer {
private List<Vehicle> vehicles;
//... getters, setters
}
Vehicle is an abstract class with a few subtypes:
public abstract class Vehicle {
}
#TypeAlias("CAR")
public class Car {
}
#TypeAlias("BOAT")
public class Boat {
}
#TypeAlias("MOTORBIKE")
public class Motorbike {
}
Is there any way to have Spring handle this use case? i.e. if I save a Car and a Boat against a customer, have them correctly hydrate when querying the Customer? At the moment, I'm getting a java.lang.InstantiationError as Spring Data seems to be trying to create an instance of the Vehicle abstract class.
Managed to resolve the issue.
Basically, I needed to add the package containing the Vehicle classes to get scanned to my Mongo Configuration class as follows:
public class CustomerDbConfig extends AbstractMongoConfiguration {
...
#Override
protected Collection<String> getMappingBasePackages() {
Collection<String> mappingBasePackages = new ArrayList<>();
mappingBasePackages.add(Vehicle.class.getPackageName());
return mappingBasePackages;
}
}
I think the above should work in most cases. My understanding is the above might not be necessary if Configuration class is within the same package as the Vehicle classes however, in my case, they're in two different packages.
Additionally, it was a bit more complicated on my side since I have multiple Mongo databases with different configurations and different MongoTemplate beans.
Initially, I was creating the MongoTemplate as follows:
#Primary
#Bean(name = "customerMongoTemplate")
public MongoTemplate customerMongoTemplate() {
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), getDatabaseName());
MappingMongoConverter converter = (MappingMongoConverter) mongoTemplate.getConverter();
converter.setCustomConversions(customConversions());
converter.afterPropertiesSet();
return mongoTemplate;
}
However, getting the MappingMongoConverter this way, via the MongoTemplate means that getMappingBasePackages was never being called. I tried to get converter instead by doing the following:
#Primary
#Bean(name = "customerMongoTemplate")
public MongoTemplate customerMongoTemplate() {
return new MongoTemplate(mongoDbFactory(), mappingMongoConverter());
}
however it didn't work, as the mongoDbFactory() and mappingMongoConverter() were returning beans for a different MongoDB configuration... This would be the ideal solution for me, but not sure how to get it working reliably with multiple configuration classes.
In the end, I managed to get working reliably as follows:
#Primary
#Bean(name = "customerMongoTemplate")
public MongoTemplate customerMongoTemplate() throws Exception {
SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient(), getDatabaseName());
MongoMappingContext mongoMappingContext = mongoMappingContext();
mongoMappingContext.setInitialEntitySet(getInitialEntitySet());
mongoMappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mongoMappingContext);
converter.setCustomConversions(customConversions());
converter.afterPropertiesSet();
return new MongoTemplate(mongoDbFactory, converter);
}
I'm not entirely comfortable with the above approach, it's somewhat finicky and could potentially cause issues with new versions of Spring, however, it does work.
Related
I'm trying to test my Spring Hateoas application, more specifically the controllers, using Springs #WebMvcTest. But I'm having problems injecting my custom RepresentationModelAssembler into the test.
First a bit of my setup:
I'm using a custom RepresentationModelAssembler to turn my DB-Models into DTOs, which have all necessary links added.
The RepresentationModelAssembler:
#Component
public class BusinessUnitAssembler implements RepresentationModelAssembler<BusinessUnit, BusinessUnitDto> {
private final Class<BusinessUnitController> controllerClass = BusinessUnitController.class;
private final BusinessUnitMapper businessUnitMapper;
public BusinessUnitAssembler(BusinessUnitMapper businessUnitMapper) {
this.businessUnitMapper = businessUnitMapper;
}
#Override
public BusinessUnitDto toModel(BusinessUnit entity) {
return businessUnitMapper.businessUnitToDto(entity)
.add(linkTo(methodOn(controllerClass).findById(entity.getId())).withSelfRel());
}
}
The BusinessUnitMapper used here is a Mapstruct mapper, which is injected by spring. In my Service I use the BusinessUnitAssembler to turn my DB-Models into DTOs, example Service method:
public Page<BusinessUnitDto> findAll(Pageable pageable) {
Page<BusinessUnit> pagedResult = businessUnitRepository.findAll(pageable);
if (pagedResult.hasContent()) {
return pagedResult.map(businessUnitAssembler::toModel);
} else {
return Page.empty();
}
}
This is how I'm doing the testing currently:
#WebMvcTest(controllers = BusinessUnitController.class)
public class BusinessUnitControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BusinessUnitService businessUnitService;
private BusinessUnitMapper mapper = Mappers.getMapper(BusinessUnitMapper.class);
private BusinessUnitAssembler assembler = new BusinessUnitAssembler(mapper);
#Test
public void getAllShouldReturnAllBusinessUnits() throws Exception {
List<BusinessUnitDto> businessUnits = Stream.of(
new BusinessUnit(1L, "Personal"),
new BusinessUnit(2L, "IT")
).map(businessUnit -> assembler.toModel(businessUnit)).collect(Collectors.toList());
when(businessUnitService.findAll(Pageable.ofSize(10))).thenReturn(new PageImpl<>(businessUnits));
mockMvc.perform(get("/businessUnits").accept(MediaTypes.HAL_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.*", hasSize(3)))
// ... do more jsonPath checking
}
}
But I'd like to have Spring inject the BusinessUnitAssembler, instead of constructing it myself. I've tried #Importing BusinessUnitAssembler as well as the BusinessUnitMapper and I've also tried it by using a custom #Configuration but I just couldn't get it to work.
So my Question is: How can I let Spring inject my BusinessUnitAssembler into the test for me instead of assembling it myself?
Additional Question: Is it valid to combine the Mapping from Database Entity to DTO in the RepresentationModelAssembler or should those two steps be kept seperate from each other?
I will try to be as detailed as possible. I have many DAO and my service needs to use one of them based on the key i get. For instance -
if(key.equals("abc") {
obj = abcDAO.getOne(id);
} else if(key.equals("xyz") {
obj = xyzDAO.getOne(id);
}
The object is of type parent class and abc, xyz.. are all child classes.
My idea is to create a Map<String, ParentCLass> to get the object just by passing the key instead of If-else so that it will be easy to add for further changes. In case it would've been a normal class, I wouldv'e initialized the map as
Map<String, ParentClass> map.
map.put("abc", new Abc());
But since DAO are interfaces and require to be #Autowired for using them, I don't know how to proceed. I am a beginner. Any help appreciated.
Spring is able to inject all beans with the same interface in a map if the map has String as key (will contain the bean names) and the interface as value.
public interface MyDao {
}
#Autowired
private Map<String, MyDao> daos;
EDIT: If you use Spring Data Repository, there is already a tagging Interface: Repository. You can use below code to inject all DAOs in one bean.
#Autowired
private Map<String, Repository> daos;
EDIT2: Example
public interface UserRepo extends JpaRepository<User, Long> { ... }
#Service
public class MyService {
#Autowired
private Map<String, Repository> daos;
public List<User> findAll() {
return daos.get("userRepo").findAll();
}
}
You can create different bean for each of your Dao with a specific name
#Bean(name = "daoImpl1")
public Dao daoImpl1(){
return new DaoImpl1();
#Bean(name = "daoImpl2")
public Dao daoImpl2(){
return new DaoImpl2();
And then #Autowire them using #Qualifier with that name
#Autowired
#Qualifier("daoImpl1")
private Dao daoImpl1;
#Autowired
#Qualifier("daoImpl2")
private Dao daoImpl2;
i made methods to make simple jpa things,
i stored all of repositories in the HashMap
you just have to pass the child class and you get the corresponding JpaRepository
you can see more at https://github.com/fajaralmu/base_web_app
example :
public List<Page> getAllPages() {
List<Page> allPages = entityRepository.findAll(Page.class);
return allPages;
}
As JPA and Spring have different context management, it is not recommended to create a data object class with both annotations #Component and #Entity.
But without #Component data object can't be injected into a service by #Autowired.
But creating new instance of my data object with new seems like a regression for me.
Is there a good way to inject a data object (#Entity) in a spring managed service ?
Data object :
#Component
#Entity
#Table(name = "user")
public class UserDo {
//data object stuff ...
Service :
#Service("listAllGoods")
#Transactional(propagation = Propagation.REQUIRED)
public class ListAllGoods implements IListGoodService{
#Autowired
private IGoodDao goodDao;
#Autowired
private UserDo user;
//option 1 : works but not recommended because forces #Component on data object
#Override
public List<GoodDo> createGood() {
user.setName("Roger");
return goodDao.create(user);
}
//option 2 :
// without #Autowired UserDo
// regression feeling
#Override
public List<GoodDO> createGood() {
UserDo user = new UserDo();
user.setName("Roger");
return goodDao.create(user);
}
The main feature of Spring is dependency injection.
Dependency or coupling, a state in which one object
uses a function of another object
It's clear that User entity is not a dependency in your situation, so it's the most correct approach to create it with a new operator.
Also, you said that you want your "dependency" to be created every time you reference your service. It's the "How to update prototype bean in a singleton" problem which you can encounter on an interview. It's not in the scope of your question, but I highly recommend you to google this.
I would like to use spring annotations in an integration test to load different instances of the same object with different applicationContexts (yet using the same configuration xml). Below is a simple example to represent my issue:
My junit test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:CarContext.xml")
public class CarRaceTest
{
#Autowired
Car car1;
#Autowired
Car car2;
#Test
public void test()
{
car1.displayParts();
car2.displayParts();
}
}
My Car class:
#Component
#Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class Car
{
#Autowired
private ElectricalSystem electricalSystem;
#Autowired
private Alternator alternator;
public void displayParts()
{
System.out.println("Parts for car: " + this.toString());
System.out.println(electricalSystem.toString());
System.out.println(alternator.toString());
}
public void raceQuarterMile()
{
}
}
ElectricalSystem class:
#Component
public class ElectricalSystem
{
#Autowired
private Alternator alternator;
}
Alternator class:
#Component
public class Alternator
{
}
contents of CarContext.xml
<context:component-scan base-package="springTest"/>
As you can see from the example for a single car the Alternator may be referenced by multiple other components/systems - all needing to reference a singleton instance. Yet for my integration test to put two cars up against each other I need each car to have its own application context so I have different alternators in each car.
This is the output I get for the above code:
Parts for car: springTest.Car#3ea20bc2
springTest.ElectricalSystem#b20dae
springTest.Alternator#29a01add
Parts for car: springTest.Car#4e43b884
springTest.ElectricalSystem#b20dae
springTest.Alternator#29a01add
Notice how my two cars have the exact same electrical systems and alternators - not what I want.
Using context.getBean with two different application contexts I have been able to get it to work as expected with the following Junit:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:CarContext.xml")
public class CarRaceTest
{
private String contextFile = "classpath:CarContext.xml";
private ApplicationContext appContext1;
private ApplicationContext appContext2;
Car car1;
Car car2;
public CarRaceTest()
{
this.appContext1 = new FileSystemXmlApplicationContext(this.contextFile);
this.appContext2 = new FileSystemXmlApplicationContext(this.contextFile);
}
#Test
public void test()
{
car1 = appContext1.getBean(Car.class);
car2 = appContext2.getBean(Car.class);
car1.displayParts();
car2.displayParts();
}
}
With the following output:
Parts for car: springTest.Car#247de4f1
springTest.ElectricalSystem#45419cee
springTest.Alternator#56a5f0c7
Parts for car: springTest.Car#2862c542
springTest.ElectricalSystem#6c8484c4
springTest.Alternator#70289784
How can I accomplish these same results with annotations without calling .getbean()?
You're correct -- Spring is unable to swap your instance level reference with a new one with a field accessor; it's set once by the #Autowired annotation, and then everything just references the singleton of that field. I believe your question can be solved using Spring's lookup-method attribute, explained with examples, here: Java code geeks
I am trying unit test spring-data-mongodb custom converter. I am following this document. As per the document there should be a method called afterMappingMongoConverterCreation in AbstractMongoConfiguration class and we need to override that method to configure custom converter. Interestingly that method is not found in the version 1.3.1. (The document is for the same version) The same document also talking about a method named setCustomConverters in MappingMongoConverter. I don't see that method also in MappingMongoConverter or it's super class. Am I missing something here? Any help is much appreciated.
If the document is outdated what is the best way to unit test customer converters? Any option other than XML configuration?
Looks like the document is bit outdated. I got it fixed using the below given code.
#EnableMongoRepositories
#ComponentScan(basePackageClasses = { ItemRepository.class })
#PropertySource("classpath:application.properties")
static class MongoConfiguration extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "scrumretro-test";
}
#Override
public Mongo mongo() {
return new Fongo("mongo-test").getMongo();
}
#Override
protected String getMappingBasePackage() {
return "com.scrumretro.repository.mongo";
}
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new ItemWriteConverter());
return new CustomConversions(converters);
}
}`