Using POST method for big queries in Spring Data Solr - java

I am using Spring Data Solr in my project. In some cases generated queries to Solr are too big (e.g.15Kb+) and cause Solr exceptions. This solution: http://codingtricks.fidibuy.com/participant/join/54fce329b760506d5d9e7db3/Spring-Data-Solr-cannot-handle-long-queries
still fails for some queries.
Since directly sending those queries to Solr via POST works fine, I chose to work in this direction. I failed to find in Spring Data Solr any way to configure the preferred method (GET/POST) for queries. Therefore, I came to the following solution: I extended SolrServer
public class CustomSolrServer extends HttpSolrServer {
public CustomSolrServer(String home, String core) {
super(home);
setCore(core);
}
#Override
public QueryResponse query(SolrParams params) throws SolrServerException {
METHOD method = METHOD.GET;
if (isBigQuery(params)) {
method = METHOD.POST;
}
return new QueryRequest( params, method ).process( this );
}
}
(some details skipped, setCore() and isBigQuery() are trivial and skipped as well)
and use it as SolrServer bean in SolrConfiguration.class:
#Configuration
#EnableSolrRepositories(basePackages = { "com.vvy.repository.solr" }, multicoreSupport=false)
#Import(value = SolrAutoConfiguration.class)
#EnableConfigurationProperties(SolrProperties.class)
public class SolrConfiguration {
#Autowired
private SolrProperties solrProperties;
#Value("${spring.data.solr.core}")
private String solrCore;
#Bean
public SolrServer solrServer() {
return new CustomSolrServer(solrProperties.getHost(),solrCore) ;
}
}
This works OK, but has a couple of drawbacks: I had to set multiCoreSupport to false. This was done because when Spring Data Solr implements repositories from the interfaces, with multiCoreSupport on it uses MultiCoreSolrServerFactory and tries to store a server per core, which is done by cloning them to the holding map. Naturally, it crashes on a customized SolrServer, because SolrServerUtils doesn't know how to clone() it. Also, I have to set core manually instead of enjoying Spring Data extracting it from #SolrDocument annotation's parameter on the entity class.
Here are the questions
1) the main and general question: is there any reasonable way to solve the problem of too long queries in Spring Data Solr (or, more specifically, to use POST instead of GET)?
2) a minor one: is there a reasonable way to customize SolrServer in Spring Data Solr and yet maintain multiCoreSupport?

Answer for Q1:Yes, u can using POST instead of GET.
Answer for Q2:Yes, u already have done a half.Except following:
1)u have to rename 'CustomSolrServer' to 'HttpSolrServer',u can check method
org.springframework.data.solr.server.support.SolrServerUtils#clone(T, java.lang.String)
for reason.
2)u don't have to specify concrete core name.U can specify core name using annotation
org.springframework.data.solr.core.mapping.SolrDocument
on corresponding solr model.
3)set multicoreSupport = true
According to your sample of classes, they should look like as following:
package com.x.x.config;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.params.SolrParams;
public class HttpSolrServer extends org.apache.solr.client.solrj.impl.HttpSolrServer {
public HttpSolrServer(String host) {
super(host);
}
#Override
public QueryResponse query(SolrParams params) throws SolrServerException {
SolrRequest.METHOD method = SolrRequest.METHOD.POST;
return new QueryRequest(params, method).process(this);
}
}
#Configuration
#EnableSolrRepositories(basePackages = { "com.vvy.repository.solr" }, multicoreSupport=true)
#Import(value = SolrAutoConfiguration.class)
#EnableConfigurationProperties(SolrProperties.class)
public class SolrConfiguration {
#Autowired
private SolrProperties solrProperties;
#Bean
public SolrServer solrServer() {
return new com.x.x.config.HttpSolrServer(solrProperties.getHost()) ;
}
}
ps: Latest spring-data-solr 3.x.x already support custom query request method,see post issue

Related

Spring Data REST custom API url for HATEOAS?

What I've known are:
If I define a custom method in the Repository interface, it will show in the URL like http://localhost/{repository}/search/{myMethod}. For example, I can define a recent method in the Order Repository interface (I can do this by using #Query simple and clean) and I can get the most recent object via http://localhost/order/search/recent.
If I use #RepositoryRestController to make a custom controller class, I have to implement HATEOAS using Resource and Resources myself and add a link. But I will get the right URL I want like http://localhost/{myPath}. For example, if I want to get the most recent objects of Order, I have to write codes below:
#RepositoryRestController
public class RecentOrdersController {
private OrderRepository orderRepo;
#Autowired
public RecentOrdersController(OrderRepository orderRepo) {
this.orderRepo = orderRepo;
}
#GetMapping(path = "/orders/recent", produces = "application/hal+json")
public ResponseEntity<Resources<OrderResource>> recentOrders() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Order> orders = orderRepo.findAll(page).getContent();
List<OrderResource> orderResources =
new OrderResourceAssembler().toResources(orders);
Resources<OrderResource> recentResources =
new Resources<OrderResource>(tacoResources);
recentResources.add(
linkTo(methodOn(RecentOrdersController.class).recentOrders())
.withRel("recents"));
return new ResponseEntity<>(recentResources, HttpStatus.OK);
}
}
and then:
#Bean
public ResourceProcessor<PagedResources<Resource<Order>>>
orderProcessor(EntityLinks links) {
return new ResourceProcessor<PagedResources<Resource<Order>>>() {
#Override
public PagedResources<Resource<Order>> process(
PagedResources<Resource<Order>> resource) {
resource.add(
links.linkFor(Order.class)
.slash("recent")
.withRel("recents"));
return resource;
}
};
}
I have to write the OrderResource and OrderResourceAssembler myself and which make this even worse is that there may many other domains like Person in the Order domain, I have to wirte the xxxResource and xxxResourceAssembler myself too.
My question is how can I combine those?
For example, if I define a domain class named Order and enable the spring data REST, how can I get the most recent orders via URL like http://localhost/recent with fully HATEOAS support at minimal effort?

Intercept the instance of an object from RestController or Hibernate in Spring before it's returned to the client

I'm developing a translation service that currently works inside another Service. For example:
public Profile getById(int chainId, int profileId, Integer languageId) {
Profile profile = profileRepository.getById(chainId, profileId);
translationService.translate(profile, languageId); // Here
return profile;
}
Now, to avoid to use a translate method on every service method of all the application, and as I only have the language of a user from the controller, I would like to execute the translate method before every Profile (and any other object) is returned to the client.
I tried to implement HandlerInterceptor in a custom interceptor, but it seems it doesn't returns the instance of the object that I'm returning. Anyone could help?
Another way to do it could be to translate every object that came from a select in Hibernate, but I also don't find any good solution to it this way...
The solution was to use Spring AOP. Probably the question wasn't very well explained, but what we needed was a way to intercept the object a user was asking to the backend, because they are able to create their own translations and we save them in the database. We had to return the model with the correct translation for each user, who has their localization in their profile. Here's the way we intercept it:
#Component
#Aspect
public class TranslatorInterceptor extends AccessApiController {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
public TranslationService translationService;
#Pointcut("execution(* com.company.project.api.controller.*.get*(..))")
public void petitionsStartWithGet() { }
#Pointcut("execution(* com.company.project.api.controller.*.list*(..))")
public void petitionsStartWithList() { }
#Pointcut("execution(* com.company.project.api.controller.*.find*(..))")
public void petitionsStartWithFind() { }
#AfterReturning(pointcut = "petitionsStartWithGet() || petitionsStartWithList() || petitionsStartWithFind()", returning = "result")
public void getNameAdvice(JoinPoint joinPoint, Object result){
translationService.translate(result, getCustomUserDetails().getLanguageId());
logger.debug("Translating " + result.getClass().toString());
}
}
What we do here is to "watch" all the methods in the package "controller" that start by 'get', 'list' or 'find' (getById(), for example) and through this advice, we intercept the object before is sent to Jackson. The method getCustomUserDetails comes from AccessApiController, which is a class we did to provide our Controllers with some information we need.

Dependency injection using Guice with the DAO pattern

For a small side project I'm working on I've been trying to implement something of a DAO pattern for my interactions with the DB, and have started using Guice (for my first time) to handle the DI for me. Right now I have this class hierarchy:
DAOImpl takes a reference to a class type so my database client (mongo/morphia) can do some initialization work and instantiate a BasicDAO provided by morphia. Here's snippets of the relevant classes:
public class DAOImpl<T> implements DAO<T> {
private static final Logger LOG = LoggerFactory.getLogger(DAOImpl.class);
private static final String ID_KEY = "id";
private final org.mongodb.morphia.dao.DAO morphiaDAO;
#Inject
public DAOImpl(Datastore ds, Class<T> resourceClass) {
morphiaDAO = new BasicDAO(resourceClass, ds);
LOG.info("ensuring mongodb indexes for {}", resourceClass);
morphiaDAO.getDatastore().ensureIndexes(resourceClass);
}
}
public class UserDAO extends DAOImpl<User> {
#Inject
public UserDAO(Datastore ds) {
super(ds, User.class);
}
public User findByEmail(String email) {
return findOne("email", email);
}
}
I know that I need to tell Guice to bind the relevant classes for each generic DAOImpl that gets extended, but I'm unsure of how to do it. This looks like it might have been answered but it's not clicking for me. I've tried some of the following:
public class AppInjector extends AbstractModule {
#Override
protected void configure() {
bind(com.wellpass.api.dao.DAO.class).to(DAOImpl.class);
// bind(new TypeLiteral<SomeInterface<String>>(){}).to(SomeImplementation.class);
// bind(new TypeLiteral<MyGenericInterface<T>>() {}).to(new TypeLiteral<MyGenericClass<T>>() {});
// bind(new TypeLiteral<DAO<User>>() {}).to(UserDAO.class);
bind(new TypeLiteral<DAO<User>>(){}).to(new TypeLiteral<DAOImpl<User>>() {});
}
}
These are some of the the errors I've seen:
com.google.inject.CreationException: Unable to create injector, see the following errors:
1) No implementation for org.mongodb.morphia.Datastore was bound.
while locating org.mongodb.morphia.Datastore
for the 1st parameter of com.wellpass.api.dao.UserDAO.<init>(UserDAO.java:12)
at com.wellpass._inject.AppInjector.configure(AppInjector.java:18)
2) java.lang.Class<T> cannot be used as a key; It is not fully specified.
at com.wellpass.api.dao.DAOImpl.<init>(DAOImpl.java:19)
at com.wellpass._inject.AppInjector.configure(AppInjector.java:14)
Any help would be much appreciated.
If you want an injection site like the following:
#Inject
public DAOConsumer(DAO<User> dao) {
}
to be injected with an instance of your UserDAO class then
bind(new TypeLiteral<DAO<User>>() {}).to(UserDAO.class);
is the correct syntax.
As for your other error:
1) No implementation for org.mongodb.morphia.Datastore was bound.
This is because Datastore is an interface. You need to bind the interface to an implementation, an instance, or a Provider<Datastore>.
To work out how to do this, think of the steps you would need to do this manually without the extra complication of Guice. Once you 100% understand this, you can try and design an object graph that appropriately reflects the steps in the initialization of morphia.
To get you started, the morphia quick tour has a guide on how to get an instance of the Datastore object:
final Morphia morphia = new Morphia();
// tell Morphia where to find your classes
// can be called multiple times with different packages or classes
morphia.mapPackage("org.mongodb.morphia.example");
// create the Datastore connecting to the default port on the local host
final Datastore datastore = morphia.createDatastore(new MongoClient(), "morphia_example");
datastore.ensureIndexes();
From their code, you can see that there are at least two dependencies required to get the Datastore:
A singleton Morphia
A singleton MongoClient
You will have to write some code to set this up, possibly using Guice's Provider class.

Spring-data-mongodb connect to multiple databases in one Mongo instance

I am using the latest spring-data-mongodb (1.1.0.M2) and the latest Mongo Driver (2.9.0-RC1). I have a situation where I have multiple clients connecting to my application and I want to give each one their own "schema/database" in the same Mongo server. This is not a very difficult task to achieve if I was using the driver directly:
Mongo mongo = new Mongo( new DBAddress( "localhost", 127017 ) );
DB client1DB = mongo.getDB( "client1" );
DBCollection client1TTestCollection = client1DB.getCollection( "test" );
long client1TestCollectionCount = client1TTestCollection.count();
DB client2DB = mongo.getDB( "client2" );
DBCollection client2TTestCollection = client2DB.getCollection( "test" );
long client2TestCollectionCount = client2TTestCollection.count();
See, easy. But spring-data-mongodb does not allow an easy way to use multiple databases. The preferred way of setting up a connection to Mongo is to extend the AbstractMongoConfiguration class:
You will see that you override the following method:
getDatabaseName()
So it forces you to use one database name. The repository interfaces that you then build use that database name inside the MongoTemplate that is passed into the SimpleMongoRepository class.
Where on earth would I stick multiple database names? I have to make multiple database names, multiple MongoTempates (one per database name), and multiple other config classes. And that still doesn't get my repository interfaces to use the correct template. If anyone has tried such a thing let me know. If I figure it out I will post the answer here.
Thanks.
Here is a link to an article I think is what you are looking for http://michaelbarnesjr.wordpress.com/2012/01/19/spring-data-mongo/
The key is to provide multiple templates
configure a template for each database.
<bean id="vehicleTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoConnection"/>
<constructor-arg name="databaseName" value="vehicledatabase"/>
</bean>
configure a template for each database.
<bean id="imageTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoConnection"/>
<constructor-arg name="databaseName" value="imagedatabase"/>
</bean>
<bean id="vehicleTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoConnection"/>
<constructor-arg name="databaseName" value="vehicledatabase"/>
</bean>
Now, you need to tell Spring where your repositories are so it can inject them. They must all be in the same directory. I tried to have them in different sub-directories, and it did not work correctly. So they are all in the repository directory.
<mongo:repositories base-package="my.package.repository">
<mongo:repository id="imageRepository" mongo-template-ref="imageTemplate"/>
<mongo:repository id="carRepository" mongo-template-ref="vehicleTemplate"/>
<mongo:repository id="truckRepository" mongo-template-ref="vehicleTemplate"/>
</mongo:repositories>
Each repository is an Interface and is written as follows (yes, you can leave them blank):
#Repository
public interface ImageRepository extends MongoRepository<Image, String> {
}
#Repository
public interface TruckRepository extends MongoRepository<Truck, String> {
}
The name of the private variable imageRepository is the collection! Image.java will be saved to the image collection within the imagedb database.
Here is how you can find, insert, and delete records:
#Service
public class ImageService {
#Autowired
private ImageRepository imageRepository;
}
By Autowiring you match the variable name to the name (id) in your configuration.
You may want to sub-class SimpleMongoDbFactory and strategize how the default DB as returned by getDb is returned. One option is to use thread-local variables to decide on the Db to use, instead of using multiple MongoTemplates.
Something like this:
public class ThreadLocalDbNameMongoDbFactory extends SimpleMongoDbFactory {
private static final ThreadLocal<String> dbName = new ThreadLocal<String>();
private final String defaultName; // init in c'tor before calling super
// omitted constructor for clarity
public static void setDefaultNameForCurrentThread(String tlName) {
dbName.set(tlName);
}
public static void clearDefaultNameForCurrentThread() {
dbName.remove();
}
public DB getDb() {
String tlName = dbName.get();
return super.getDb(tlName != null ? tlName : defaultName);
}
}
Then, override mongoDBFactory() in your #Configuration class that extends from AbstractMongoConfiguration like so:
#Bean
#Override
public MongoDbFactory mongoDbFactory() throws Exception {
if (getUserCredentials() == null) {
return new ThreadLocalDbNameMongoDbFactory(mongo(), getDatabaseName());
} else {
return new ThreadLocalDbNameMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials());
}
}
In your client code (maybe a ServletFilter or some such) you will need to call:
ThreadLocalDBNameMongoRepository.setDefaultNameForCurrentThread()
before doing any Mongo work and subsequently reset it with:
ThreadLocalDBNameMongoRepository.clearDefaultNameForCurrentThread()
after you are done.
So after much research and experimentation, I have concluded that this is not yet possibly with the current spring-data-mongodb project. I tried baja's method above and ran into a specific hurdle. The MongoTemplate runs its ensureIndexes() method from within its constructor. This method calls out the the database to make sure annotated indexes exist in the database. The constructor for MongoTemplate gets called when Spring starts up so I never even have a chance to set a ThreadLocal variable. I have to have a default already set when Spring starts, then change it when a request comes in. This is not allowable because I don't want nor do I have a default database.
All was not lost though. Our original plan was to have each client running on its own application server, pointed at its own MongoDB database on the MongoDB server. Then we can provide a -Dprovider= system variable and each server runs pointing only to one database.
We were instructed to have a multi-tenant application, hence the attempt at the ThreadLocal variable. But since it did not work, we were able to run the application the way we had originally designed.
I believe there is a way though to make this all work, it just takes more than is described in the other posts. You have to make your own RepositoryFactoryBean. Here is the example from the Spring Data MongoDB Reference Docs. You would still have to implement your own MongoTemplate and delay or remove the ensureIndexes() call. But you would have to rewrite a few classes to make sure your MongoTemplate is called instead of Spring's. In other words, a lot of work. Work that I would like to see happen or even do, I just did not have the time.
Thanks for the responses.
The spot to look at is the MongoDbFactory interface. The basic implementation of that takes a Mongo instance and works with that throughout all the application lifetime. To achieve a per-thread (and thus per-request) database usage you'll probably have to implement something along the lines of AbstractRoutingDataSource. The idea is pretty much that you have a template method that will have to lookup the tenant per invocation (ThreadLocal bound I guess) and then select a Mongo instance from a set of predefined ones or some custom logic to come up with a fresh one for a new tenant etc.
Keep in mind that MongoDbFactory usually get's used through the getDb() method. However, there are features in MongoDB that need us to provide a getDb(String name). DBRefs (sth. like a foreign key in the relational world) can point to documents an entirely different database. So if you're doing the delegation either avoid using that feature (I think the DBRefs pointing to another DB are the only places calling getDb(name)) or explicitly handle it.
From a configuration point of view you could either simply override mongoDbFactory() entirely or simply not extend the base class at all and come up with your own Java based configuration.
I used different DB using java Config, this is how i did it:
#Bean
public MongoDbFactory mongoRestDbFactory() throws Exception {
MongoClientURI uri=new MongoClientURI(environment.getProperty("mongo.uri"));
return new SimpleMongoDbFactory(uri);
}
#Override
public String getDatabaseName() {
return "rest";
}
#Override
public #Bean(name = "secondaryMongoTemplate") MongoTemplate mongoTemplate() throws Exception{ //hay que cambiar el nombre de los templates para que el contendor de beans sepa la diferencia
return new MongoTemplate(mongoRestDbFactory());
}
And the other was like this:
#Bean
public MongoDbFactory restDbFactory() throws Exception {
MongoClientURI uri = new MongoClientURI(environment.getProperty("mongo.urirestaurants"));
return new SimpleMongoDbFactory(uri);
}
#Override
public String getDatabaseName() {
return "rest";
}
#Override
public #Bean(name = "primaryMongoTemplate") MongoTemplate mongoTemplate() throws Exception{
return new MongoTemplate(restDbFactory());
}
So when i need to change my database i only select which Config to use
An example with Spring boot V2.6.2 :
Content of your "application.yml" file :
spring:
application:
name: myApp
autoconfigure:
data:
mongodb:
host: localhost
port: 27017
database: FirstDatabase
mongodbreference:
host: localhost
port: 27017
database: SecondDatabase
In a Classe named "MultipleMongoProperties.java" :
package your.packagename;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
#Data
#ConfigurationProperties(prefix = "spring.data")
public class MultipleMongoProperties {
private MongoProperties mongodb = new MongoProperties();
private MongoProperties mongodbreference = new MongoProperties();
}
And finaly the class "MultipleMongoConfig.java" :
package your.package;
import com.mongodb.client.MongoClients;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(MultipleMongoProperties.class)
public class MultipleMongoConfig {
private static final Logger LOG = LoggerFactory.getLogger(Multip
leMongoConfig.class);
private final MultipleMongoProperties mongoProperties;
private MongoProperties mongoDestination;
#Bean("referenceMongoTemplate")
#Primary
public MongoTemplate referenceMongoTemplate() {
return new MongoTemplate(referenceFactory(this.mongoProperties.getMongodbreference()));
}
#Bean("destinationMongoTemplate")
public MongoTemplate destinationMongoTemplate() {
return new MongoTemplate(destinationFactory(this.mongoProperties.getMongodb()));
}
public MongoDatabaseFactory referenceFactory(final MongoProperties mongo) {
this.setUriToMongoProperties(mongo);
return new SimpleMongoClientDatabaseFactory(MongoClients.create(mongo.getUri()), mongo.getDatabase());
}
public MongoDatabaseFactory destinationFactory(final MongoProperties mongo) {
this.setUriToMongoProperties(mongo);
return new SimpleMongoClientDatabaseFactory(MongoClients.create(mongo.getUri()), mongo.getDatabase());
}
private void setUriToMongoProperties(MongoProperties mongo) {
mongo.setUri("mongodb://" + mongo.getUsername() + ":" + String.valueOf(mongo.getPassword()) + "#" + mongo.getHost() + ":" + mongo.getPort() + "/" + mongo.getAuthenticationDatabase());
}
}
In another class you just have to implement :
package your.package;
import com.mongodb.bulk.BulkWriteResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
#Component
public class CollectionRepositoryImpl implements CollectionsRepository {
#Autowired
#Qualifier("referenceMongoTemplate")
private MongoTemplate referenceMongoTemplate;
#Autowired
#Qualifier("destinationMongoTemplate")
private MongoTemplate destinationMongoTemplate;
...
As far as I understand, you want more flexibility in changing the current db on the fly.
I've linked a project that implements multi tenancy in a simple way.
It could be used as a starting point for the application.
It implements SimpleMongoDbFactory and provide a custom getDB method to resolve the correct db to use in certain moment. It can be improved in many ways, for example, by retrieving the db details from a HttpSession from SpringSession object, which for instance could be cached by Redis .
To have different mongoTemplates using different dbs at the same time, maybe change the scope of your mongoDbFactory to session.
References:
multi-tenant-spring-mongodb

TransactionScope class equivalent in Spring Framework?

What would be a somewhat equivalent class for TransactionScope(.Net) in Spring.
I know absolutely nothing about .Net, so I'm not certain if this is what you're looking for. You can use SimpleTransactionScope in order to maintain objects across the lifecycle of a transaction. It is not registered by default, so you will have to register it with spring core like any custom scope, and give it a stringy name. Then if you want you can also create an annotation specifically to register one.
It was my issue that suggested this a few years ago, after this question was created for certain. We requested it specifically for timestamps across multiple methods for injection in a service. You can do something like this.
public class SimpleTransactionFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope("transaction", new SimpleTransactionScope());
}
}
#Configuration
class MyConfiguration {
#Scope(scopeName = "transaction")
#Bean
Instant nowInstant() {
return Instant.now();
}
}
#Service
class MyService {
private final ObjectFactory<Instant> nowFactory;
MyService( #Qualifier("nowInstant") ObjectFactory<Instant> nowFactory ) {
this.nowFactory = nowfactory
}
#Transactional
public boolean nowisEqualAlways() {
var now = nowFactory.getObject();
var sameNow = nowFactory.getObject();
return Objects.equals( now, sameNow );
}
}
If you don't do this, your now could actually change during your transaction by a small amount of time. You can test that simply by spamming now calls in a test.
It may not be required for your needs, so it's hard for me to tell (obviously your needs are probably long past, hopeful.y this helps someone in the future though)
The #Transactional annotation looks equivalent.
This can be placed on classes and methods and can be defined with propagation, isolation, rollback etc.

Categories