How using #InsertOnlyProperty with Spring Boot 2.7 - spring-data-jdbc

I'm going to use #InsertOnlyProperty with Spring Boot 2.7 as it will take time for us to migrate to Spring Boot 3.0!
So I'm going to create my DataAccessStrategy based on the DefaultAccessStrategy and also override the SqlParametersFactory so that I can pass the RelationalPersistentProperty::isInsertOnly condition to the getParameterSource method, also overriding RelationalPersistentProperty by adding isInsertOnly. And is there a way to override RelationalPersistentProperty to add isInsertOnly property. Am I correct or is there a better solution than switching to Spring Boot 3.0 now. Thank you!

Since #InsertOnlyProperty is only supported for the aggregate root (in Spring Boot 3.0), one approach could be to copy the data to a surrogate object and use a custom method to save it. It would look something like this:
public record MyAggRoot(#Id Long id,
/* #InsertOnlyProperty */ Instant createdAt, int otherField) {}
public interface MyAggRootRepository
extends Repository<MyAggRoot, Long>, MyAggRootRepositoryCustom { /* ... */ }
public interface MyAggRootRepositoryCustom {
MyAggRoot save(MyAggRoot aggRoot);
}
#Component
public class MyAggRootRepositoryCustomImpl implements TaskRepositoryCustom {
#Autowired
private final JdbcAggregateOperations jao;
// Override table name which would otherwise be derived from the class name
#Table("my_agg_root")
private record MyAggRootForUpdate(#Id Long id, int otherField) {}
#Override
public MyAggRoot save(MyAggRoot aggRoot) {
// If this is a new instance, insert as-is
if (aggRoot.id() == null) return jao.save(aggRoot);
// Create a copy without the insert-only field
var copy = new MyAggRootForUpdate(aggRoot.id(), aggRoot.otherField());
jao.update(copy);
return aggRoot;
}
}
It is however a bit verbose so it would only be a reasonable solution if you only need it in a few places.

Related

How to initiate a global method that can be accessed by all controllers in Spring Boot

I would like to initiate a simple POJO that generates an array of random Strings when initiated (or when the Spring Boot application starts). This array of Strings has to be shared across every controller in the Spring Boot application, and it cannot be different for different controllers. The class and methods (of this shared POJO) are internal to a Spring Boot application and are accessed by calling the getters in the controller methods (only).
Furthermore, I would like to avoid using the application.properties. The best solution would be java-only (no database such as H2 or offloading the POJO onto a file). Also, using the sessions will not help.
Something like this would help:
http://www.masterspringboot.com/security/authentication/securing-spring-boot-with-in-memory-basic-authentication
How can I accomplish such a design ?
My idea is to simply use a micro-service and launch it separately, but I would like to confirm if there is something else I can do within the single Spring Boot application.
You can create a singleton class that holds your values:
public class Main {
public static void main(String[] args) {
DataHolder dataHolder = DataHolder.getInstance();
String[] arr = dataHolder.getArr();
}
}
class DataHolder {
private static DataHolder instance = null;
private String[] arr = new String[10];
public static DataHolder getInstance() {
if (instance == null)
instance = new DataHolder();
return instance;
}
private DataHolder() {
fillArray();
}
private void fillArray() {
// use this method to fill your array
}
public String[] getArr() {
return arr;
}
}
To answer my own question, the solution was to use the in-memory provided by the mapDB, and generate all the data during the spring-boot initiating itself...
https://mvnrepository.com/artifact/org.mapdb/mapdb
The source code example can be found using the search engines...

Table name configured with external properties file

I build a Spring-Boot application that accesses a Database and extracts data from it. Everything is working fine, but I want to configure the table names from an external .properties file.
like:
#Entity
#Table(name = "${fleet.table.name}")
public class Fleet {
...
}
I tried to find something but I didn't.
You can access external properties with the #Value("...") annotation.
So my question is: Is there any way I can configure the table names? Or can I change/intercept the query that is sent by hibernate?
Solution:
Ok, hibernate 5 works with the PhysicalNamingStrategy. So I created my own PhysicalNamingStrategy.
#Configuration
public class TableNameConfig{
#Value("${fleet.table.name}")
private String fleetTableName;
#Value("${visits.table.name}")
private String visitsTableName;
#Value("${route.table.name}")
private String routeTableName;
#Bean
public PhysicalNamingStrategyStandardImpl physicalNamingStrategyStandard(){
return new PhysicalNamingImpl();
}
class PhysicalNamingImpl extends PhysicalNamingStrategyStandardImpl {
#Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
switch (name.getText()) {
case "Fleet":
return new Identifier(fleetTableName, name.isQuoted());
case "Visits":
return new Identifier(visitsTableName, name.isQuoted());
case "Result":
return new Identifier(routeTableName, name.isQuoted());
default:
return super.toPhysicalTableName(name, context);
}
}
}
}
Also, this Stackoverflow article over NamingStrategy gave me the idea.
Table names are really coming from hibernate itself via its strategy interfaces. Boot configures this as SpringNamingStrategy and there were some changes in Boot 2.x how things can be customised. Worth to read gh-1525 where these changes were made. Configure Hibernate Naming Strategy has some more info.
There were some ideas to add some custom properties to configure SpringNamingStrategy but we went with allowing easier customisation of a whole strategy beans as that allows users to to whatever they need to do.
AFAIK, there's no direct way to do config like you asked but I'd assume that if you create your own strategy you can then auto-wire you own properties to there. As in those customised strategy interfaces you will see the entity name, you could reserve a keyspace in boot's configuration properties to this and match entity names.
mytables.naming.fleet.name=foobar
mytables.naming.othertable.name=xxx
Your configuration properties would take mytables and within that naming would be a Map. Then in your custom strategy it would simply be by checking from mapping table if you defined a custom name.
Spring boot solution:
Create below class
#Configuration
public class CustomPhysicalNamingStrategy extends SpringPhysicalNamingStrategy{
#Value("${table.name}")
private String tableName;
#Override
public Identifier toPhysicalTableName(final Identifier identifier, final JdbcEnvironment jdbcEnv) {
return Identifier.toIdentifier(tableName);
}
}
Add below property to application.properties:
spring.jpa.properties.hibernate.physical_naming_strategy=<package.name>.CustomPhysicalNamingStrategy
table.name=product

Dynamic Index with SpringData ElasticSearch

How can I parameterize a SpringData ElasticSearch index at runtime?
For example, the data model:
#Document(indexName = "myIndex")
public class Asset {
#Id
public String id;
// ...
}
and the repository:
public interface AssetRepository extends ElasticsearchCrudRepository<Asset, String> {
Asset getAssetById(String assetId);
}
I know I can replace myIndex with a parameter, but that parameter will be resolved during instantiation / boot. We have the same Asset structure for multiple clients / tenants, which have their own index. What I need is something like this:
public interface AssetRepository extends ElasticsearchCrudRepository<Asset, String> {
Asset getAssetByIdFromIndex(String assetId, String index);
}
or this
repoInstance.forIndex("myOtherIndex").getAssetById("123");
I know this does not work out of the box, but is there any way to programmatically 'hack' it?
Even though the bean is init at boot time, you can still achieve it by spring expression language:
#Bean
Name name() {
return new Name();
}
#Document(indexName="#{name.name()}")
public class Asset{}
You can change the bean's property to change the index you want to save/search:
assetRepo.save(new Asset(...));
name.setName("newName");
assetRepo.save(new Asset(...));
What should be noticed is not to share this bean in multiple thread, which may mess up your index.
Here is a working example.
org.springframework.data.elasticsearch.repository.ElasticSearchRepository has a method
FacetedPage<T> search(SearchQuery searchQuery);
where SearchQuery can take multiple indices to be used for searching.
I hope it answers

Error while using parameter in #Scheduled in a spring 4.1 application

I have Spring 4.1 Application. I am trying to schedule based on value from property file. I have read this post. But I do not want the following way of EL inside #Scheduled
#Scheduled(fixedDelayString = "${my.fixed.delay.prop}")
public void readLog() {
...
}
Here is my class.
public class MyService {
#Value("${timerInMilliSeconds: 60000}")
private long timerinMilliSeconds;
public myService(){
}
#Scheduled(fixedRate = timerinMilliSeconds)
public void myTimer() {
//do stuff
}
}
I get this error.
The value for annotation attribute Scheduled.fixedRate must be a constant
expression
You can't do that; it's a limitation of the way annotations work - Strings in annotations have to be constants (they are stored in the class and can't be different for each instance).
By the way ${my.fixed.delay.prop} is not "EL", it's a property placeholder.

spring mvc 3 caching example

I have requirement for spring mvc 3 caching. Requirement is : while starting the server, we need to call database for one dropdown and put those values in the cache. So that whenever we required those values, we need to retrieve from cache.
Please help me with an example.
Thanks in advance.
May be you can use init-method (Spring 2.5) or #PostConstruct annotation (in Spring 3.0).
This method will be called during server start up
The following is code snippet
#Component
public class CacheDBData {
private String values[];
//add setter & getter
//This will be called during server start up after properties are initialised
#PostConstruct
public void getDataFromDB() {
values = //Logic to get data from DB and store that in values property
}
}
Suppose for example you can use in class as follows
#controller
public class HomeController {
#Autowired
private CacheDBData cacheDBData ;
//getter and setters
private void methodxyz() {
String values[] = cacheDBData.getValues();
}
}
I've had success with Ehcahe for Spring. There's a couple of config files to setup but after that you simply annotate the methods you want to cache the output from and it just works.
This has the advantage that you can change the values coming back from the service/database and NOT have to restart your app, unlike the accepted answer.

Resources