Does Spring Data JDBC have anything similar to #PostLoad and #PrePersist from Spring Data JPA?
With Spring Data JDBC you currently can't annotate the entity directly. But there are life cycle listeners and callbacks that you can use for the same purpose.
One of the examples given:
#Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> {
#Override
public Person onBeforeConvert(User user) {
return // ...
}
#Override
public Person onBeforeSave(User user) {
return // ...
}
}
Related
Spring Boot version - 2.4.4,
mongodb version - 4.4.4
In my project, I want to do entry in 2 different document of mongodb, but if one fails than it should do rollback. mongodb supports transaction after version 4.0 but only if you have at least one replica set.
In my case I don't have replica set and also cannot create it according to my project structure. I can't use transaction support of mongodb because no replica-set. So, I am using Spring Transaction.
According to spring docs, to use transaction in Spring Boot, you only need to use #transactional annotation and everything will work(i.e. rollback or commit).
I tried many things from many sources but it is not rollbacking transaction if one fail.
Demo code is here,
This is demo code, not actual project.
This is my service class.
#Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
UserDetailRepository userDetailRepository;
#Transactional(rollbackFor = Exception.class)
public ResponseEntity<JsonNode> createUser(SaveUserDetailRequest saveUserDetailRequest) {
try {
User _user = userRepository.save(new User(saveUserDetailRequest.getId(), saveUserDetailRequest.getFirstName(), saveUserDetailRequest.getLastName()));
UserDetail _user_detail = userDetailRepository.save(new UserDetail(saveUserDetailRequest.getPhone(), saveUserDetailRequest.getAddress()));
} catch (Exception m) {
System.out.print("Mongo Exception");
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Also tried below code but still not working,
#EnableTransactionManagement
#Configuration
#EnableMongoRepositories({ "com.test.transaction.repository" })
#ComponentScan({"com.test.transaction.service"})
public class Config extends AbstractMongoClientConfiguration{
private com.mongodb.MongoClient mongoClient;
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
#Bean
public com.mongodb.MongoClient mongodbClient() {
mongoClient = new com.mongodb.MongoClient("mongodb://localhost:27017");
return mongoClient;
}
#Override
protected String getDatabaseName() {
return "test";
}
}
The transaction support in Spring is only there to make things easier, it doesn't replace the transaction support for the underlying datastore being used.
In this case, it will simply delegate the starting/committing of a transaction to MongoDB. WHen using a database it will eventually delegate to the database etc.
As this is the case, the pre-requisites for MongoDB still need to be honoured and you will still need a replica.
I'm developing Spring Boot application which persists data into MS SQL database. I'm tasked to add support for PostgreSQL, which uses same tables. So my goal is to add another repository layer implementation. But things gets little bit complicated.
It would be great if my repository layer could look like this:
public interface RecordRepository {
Record get(long id);
}
#Repository
#Conditional(MsSqlCondition.class)
public interface MsSqlRecordRepository {
public Record get(long id) {
// MS SQL implementation...
}
}
#Repository
#Conditional(PostgreSqlCondition.class)
public interface PostgreSqlRecordRepository {
public Record get(long id) {
// PostgreSql implementation...
}
}
However, it seems to not be possible in my case.
First of all, my application doesn't have database configuration in application.yaml file. It has to get these variables from remote HTTP server. My #Configuration file looks something like this:
#Configuration
public class MyAppConfiguration {
#Bean
public DatabaseConfiguration databaseConfiguration() {
// Make HTTP request to remote server
if (something) {
return new MsSqlServerConfiguration(...);
} else {
return new PostgreSqlServerConfiguration(...);
}
}
}
With this approach, I'm unable to use #Conditional annotation for my DataSource and repository beans, since #Conditional is evaluated while parsing #Configuration files. I need to choose right implementation AFTER DatabaseConfiguration bean is created.
I already considered this approach:
#Configuration
public class RepositoryConfiguration {
#Bean
public RecordRepository(DatabaseConfiguration configuration, JdbcTemplate jdbcTemplate) {
if (configuration.getType() == MS_SQL) {
return new MsSqlRecordRepository(jdbcTemplate);
} else {
return new PostgreSqlRecordRepository(jdbcTemplate);
}
}
}
But this seems that it is not working for me either, sice I'm using Spring AOP for my repository classes.
Does Spring Boot have some other mechanism, which allows me to choose right repository implementation after my DatabaseConfiguration bean is created?
Thanks
I have a legacy application with a database that splits up the data into multiple schemas on the same physical database. The schemas are identical in structure.
I use a microservice using Spring Boot Data JPA to do work on a single schema. Then to avoid code repetition, I created a router service that forwards the request to the single schema microservice replica each with a different database connection. But I found that a bit overkill (but works)
I am trying to reduce it back down to a single microservice. I haven't been successful yet, but I set up the tables with the schema property.
#Table(
name = "alerts",
schema = "ca"
)
However, it gets confused when I try to do inheritance and #MappedSuperclass to reduce the code duplication.
In addition the #OneToMany breaks apart because of the inheritance getting errors like X references an unknown entity: Y
Basically is there a way of using inheritance on JPA that uses the same table structure with the difference being just the schema without copy and pasting too much code. Ideally I'd like to just pass a "schema" parameter to a DAO and it somehow does it for me.
In the end, we just need a data source that would route according to the situation. To do this a #Component that extends AbstractRoutingDataSource is used and a ThreadLocal to store the request context.
The ThreadLocal would be something like this (examples are using Lombok)
#AllArgsConstructor
public class UserContext {
private static final ThreadLocal<UserContext> context =
new ThreadLocal<>();
private final String schema;
public static String getSchema() {
return context.get().schema;
}
public static void setFromXXX(...) {
context.set(new UserContext(
...
));
}
}
A source for the data sources would be needed:
#Configuration
public class DataSources {
#Bean
public DataSource schema1() {
return build("schema1");
}
#Bean
public DataSource schema2() {
return build("schema2");
}
private DataSource buildDataSource(String schema) {
...
return new DriverManagerDataSource(url, username, password);
}
}
And finally the router which is marked as the #Primary data source to make sure it is the one that gets used by JPA.
#Component
#Primary
public class RoutingDatasource extends AbstractRoutingDataSource {
#Autowired
#Qualifier("schema1")
private DataSource schema1;
#Autowired
#Qualifier("schema2")
private DataSource schema2;
#Override
public void afterPropertiesSet() {
setTargetDataSources(
Map.of(
"schema1", schema1,
"schema2", schema2
)
);
super.afterPropertiesSet();
}
#Override
protected Object determineCurrentLookupKey() {
return UserContext.getSchema();
}
}
This avoids the code duplication when all that is different is a schema or even a data source.
I have a springboot application, with its own datasource (let's call DB1) set on properties working fine.
But this application needs do configure a new datasource (DB2), using some parameters the user have informed before and stored in DB1.
My idea is to create a named bean, so a specific part of my application can use to access DB2 tables. I think it is possible to do that by restarting the application, but I would like to avoid it though.
Besides, I need that some part of my code use the new datasource (spring data jpa, mappings, and so on). I don't know if this matter, but it is a web application, so I cannot create the datasource only for the request thread.
Can you help me?
Thanks in advance.
Spring has dynamic datasource routing if that's where you are headed. In my case it is the same schema (WR/RO)
public class RoutingDataSource extends AbstractRoutingDataSource {
#Autowired
private DataSourceConfig dataSourceConfig;
#Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
public enum DbType {
MASTER, WRITE, READONLY,
}
Then you need a custom annotation and an aspect
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ReadOnlyConnection {
}
#Aspect
#Component
#Order(1)
public class ReadOnlyConnectionInterceptor {
Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {}
#Around("#annotation(readOnlyConnection)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
Object result = null;
try {
DbContextHolder.setDbType(DbType.READONLY);
result = proceedingJoinPoint.proceed();
DbContextHolder.clearDbType();
return result;
} finally {
DbContextHolder.clearDbType();
}
}
}
And then you can act on you DB with the tag #ReadOnlyConnection
#Override
#Transactional(readOnly = true)
#ReadOnlyConnection
public UnitDTO getUnitById(Long id) {
return unitRepository.findOne(id);
}
An example can be found here: https://github.com/afedulov/routing-data-source.
I used that as a basis for my work although it is still in progress because I still need to resolve runtime dependencies ( i.e. hibernate sharding ).
I need to be able to store the HTTP Session in a relational database in order to do stateless load balancing of my front-end users across multiple front-end servers. How can I achieve this in Spring 4?
I see how one can do this with Redis, however there does not appear to be documentation on how to do this with a relational database e.g. Postgres.
With Spring Session (it transparently will override HttpSessions from Java EE) you can just take SessionRepository interface and implement it with your custom ex. JdbcSessionRepository. It is kind of easy to do. When you have your implementation, then just add manually (you don't need #EnableRedisHttpSession annotation) created filter to filter chain, like bellow:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//other stuff...
#Autowired
private SessionRepository<ExpiringSession> sessionRepository;
private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy(); // or HeaderHttpSessionStrategy
#Bean
public SessionRepository<ExpiringSession> sessionRepository() {
return new JdbcSessionRepository();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
SessionRepositoryFilter<ExpiringSession> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
http
.addFilterBefore(sessionRepositoryFilter, ChannelProcessingFilter.class);
}
}
Here you have how SessionRepository interface looks like. It has only 4 methods to implement. For how to create Session object, you can look in MapSessionRepository and MapSession implementation (or RedisOperationsSessionRepository and RedisSession).
public interface SessionRepository<S extends Session> {
S createSession();
void save(S session);
S getSession(String id);
void delete(String id);
}
Example solution https://github.com/Mati20041/spring-session-jpa-repository
Now spring boot supports by 'spring-session-jdbc'. You can save session into db with less code. For more example you can look at https://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot-jdbc.html#httpsession-jdbc-boot-sample
Just slap Spring Session on it, and you're done. Adding a Redis client bean and annotating a configuration class with #EnableRedisHttpSession is all you need.