Multiple databases in dropwizard - java

I am trying to read and write into two databases and I am running into issues:
config.yml
database1:
driverClass: com.mysql.jdbc.Driver
user: user1
password: user!23
url: jdbc:mysql://url.to.connect:3306/db1
properties: charSet: UTF-8
maxWaitForConnection: 1s
minSize: 8
maxSize: 32
checkConnectionWhileIdle: false
checkConnectionHealthWhenIdleFor: 10s
closeConnectionIfIdleFor: 1 minute
database2:
driverClass: com.mysql.jdbc.Driver
user: user2
password: user!23
url: jdbc:mysql://url.to.connect:3306/db2
properties: charSet: UTF-8
maxWaitForConnection: 1s
minSize: 8
maxSize: 32
checkConnectionWhileIdle: false
checkConnectionHealthWhenIdleFor: 10s
closeConnectionIfIdleFor: 1 minute
configuration.java
public class ExampleConfiguration extends Configuration {
#Valid
#NotNull
private DataSourceFactory database1 = new DataSourceFactory();
#Valid
#NotNull
private DataSourceFactory database2 = new DataSourceFactory();
#JsonProperty("database1")
public DataSourceFactory getDb1DataSourceFactory() {
return database1;
}
#JsonProperty("database2")
public DataSourceFactory getDb2DataSourceFactory() {
return database2;
}
}
Now following: Maintain multiple migration files in parallel in dropwizard, I tried to add a bootstrap bundle as follows:
#Override
public void initialize(Bootstrap< ExampleConfiguration > bootstrap) {
bootstrap.addCommand(new RenderCommand());
bootstrap.addBundle(new AssetsBundle());
bootstrap.addBundle(new MigrationsBundle<ExampleConfiguration>() {
#Override
public DataSourceFactory getDataSourceFactory(ExampleConfiguration configuration) {
return configuration.getDb1DataSourceFactory();
}
});
bootstrap.addBundle(new MigrationsBundle<ExampleConfiguration>() {
#Override
public DataSourceFactory getDataSourceFactory(ExampleConfiguration configuration) {
return configuration.getDb2DataSourceFactory();
}
});
bootstrap.addBundle(hibernateBundle);
}
But I got command 'db' has been already used. I am still unclear what is happening in initialize(). How can I switch between DB instances for read and write operations?
Thank you.

You should override each MigrationsBundle implementation's method name to provide unique command's name. Then using CLI you will need to specify correct name to use according database configuration.

Related

Spring JPA - Repository Query is returning 0 rows

I have this repository class:
public interface ShiftRepository extends CrudRepository<Shift, Long> {
#Query("SELECT s FROM Shift s")
public List<Shift> getAllShifts();
}
All I want it to do is grab all shifts.
The Service calling it is just doing this:
public List<Shift> getAllShifts() {
return shiftRepository.getAllShifts();
}
And the Controller is just doing this:
public ResponseEntity<List<Shift>> getAllShifts() {
List<Shift> shifts;
try {
shifts = shiftService.getAllShifts();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ArrayList<>());
}
if (shifts != null) {
System.out.println(shifts.size());
return ResponseEntity.status(HttpStatus.OK).body(shifts);
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ArrayList<>());
}
}
On the frontend I am getting this response.
Empty array of shifts
succesful 200 response
So I don't know what the issue is, I just want all the records. Using Spring boot and Heroku Postgres
application.yml:
spring:
jpa:
properties:
hibernate.default_schema: dev_env
hibernate:
ddl-auto: create-drop
database-platform: org.hibernate.dialect.PostgreSQLDialect
datasource:
hikari:
schema: dev_env
driverClassName: org.postgresql.Driver
type: org.apache.tomcat.jdbc.pool.DataSource
Your problem seems like in that Query annotation. You have to define value tag before your query. As:
#Query(value = "SELECT s FROM Shift s")
Sorry for wasting time. It's late was very enthralled in Spring boot and React debugging my application. Did not realize I had not populated database with test data XD

Migration from Flyway 4 to 8 SqlMigrationResolver

I am beginner programmer. I am working in project with flyway-core 4.0.3 and I am migrating it to flyway 8.0.5. One thing is left and I don't know how to migrate this. I am pasting a class - this is a class that filters sql scripts. Apart from standard locations parameter we have also filtering mechanism based on some file name conditions (not prefix and not suffix). I am using free version of flyway, that's why I cannot use some features like placeholders.
This is a piece of code that filters scripts choosing only those that has DDL in name, just an example for business filter. There would be other things to filter in file name also..
try (Connection connectionMetaDataTable = JdbcUtils.openConnection(dataSource)) {
DbSupport dbSupport = DbSupportFactory.createDbSupport(connectionMetaDataTable, true);
String scriptsLocation = prepareLocation();
Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.setLocations(scriptsLocation);
flyway.setEncoding("UTF-8");
CustomMigrationResolver customMigrationResolver = new CustomMigrationResolver (
flyway,
dbSupport,
new Scanner(Thread.currentThread().getContextClassLoader()),
new Location(scriptsLocation));
flyway.setResolvers(customMigrationResolver );
flyway.setSkipDefaultResolvers(true);
flyway.migrate();
} catch (SQLException e) {
LOGGER.error(e.getMessage(), e);
}
public class CustomMigrationResolver extends SqlMigrationResolver {
public CustomMigrationResolver(Flyway flyway, DbSupport dbSupport, Scanner scanner, Location location) {
super(dbSupport, scanner, location, PlaceholderReplacer.NO_PLACEHOLDERS, flyway.getEncoding(), flyway.getSqlMigrationPrefix(), flyway.getRepeatableSqlMigrationPrefix(),
flyway.getSqlMigrationSeparator(), flyway.getSqlMigrationSuffix());
}
#Override
public List<ResolvedMigration> resolveMigrations() {
List<ResolvedMigration> allMigrations = super.resolveMigrations();
List<ResolvedMigration> onlyDdlMigrations = allMigrations.stream().filter(m -> m.getDescription().startsWith("DDL")).collect(Collectors.toList()); //some shortened business filtering condiitons
return onlyDdlMigrations;
}
In flyway 8.0.4 my customMigrationResolver could look like this:
public class Flyway8CustomMigrationResolver extends SqlMigrationResolver {
public Flyway8CustomMigrationResolver(ResourceProvider resourceProvider,
SqlScriptExecutorFactory sqlScriptExecutorFactory,
SqlScriptFactory sqlScriptFactory,
Configuration configuration,
ParsingContext parsingContext) {
super(resourceProvider, sqlScriptExecutorFactory, sqlScriptFactory, configuration, parsingContext);
}
#Override
public List<ResolvedMigration> resolveMigrations(Context context) {
List<ResolvedMigration> allMigrations = super.resolveMigrations(context);
//some business filtering of the files..
return onlyDdlMigrations;
}
}
There are no examples how to provide all parameters for Flyway8CustomMigrationResolver constructor, can you help me?

Is there anyway to disable "Retryable writes" to false in Spring Boot 2.2.1

First time
I am trying to develop a controller to save data in DocumentDB in AWS.
In the first time it saves, but in the second time, I am looking for this register saved in database, I got this and change some data, and save, but...
I am getting this error:
Caused by: com.mongodb.MongoCommandException: Command failed with error 301: 'Retryable writes are not supported' on server aws:27017. The full response is {"ok": 0.0, "code": 301, "errmsg": "Retryable writes are not supported", "operationTime": {"$timestamp": {"t": 1641469879, "i": 1}}}
This my java code
#Service
public class SaveStateHandler extends Handler<SaveStateCommand> {
#Autowired
private MongoRepository repository;
#Autowired
private MongoTemplate mongoTemplate;
#Override
public String handle(Command command) {
SaveStateCommand cmd = (SaveStateCommand) command;
State state = buildState(cmd);
repository.save(state);
return state.getId();
}
private State buildState(SaveStateCommand cmd) {
State state = State
.builder()
.activityId(cmd.getActivityId())
.agent(cmd.getAgent())
.stateId(cmd.getStateId())
.data(cmd.getData())
.dataAlteracao(LocalDateTime.now())
.build();
State stateFound = findState(cmd);
if (stateFound != null) {
state.setId(stateFound.getId());
}
return state;
}
private State findState(SaveStateCommand request) {
Query query = new Query();
selectField(query);
where(request, query);
return mongoTemplate.findOne(query, State.class);
}
private void selectField(Query query) {
query.fields().include("id");
}
private void where(SaveStateCommand request, Query query) {
query.addCriteria(new Criteria().andOperator(
Criteria.where("activityId").is(request.getActivityId()),
Criteria.where("agent").is(request.getAgent())));
}
}
In AWS they suggest to use retryWrites=false but I donĀ“t know how to do it in Spring Boot.
I use Spring Boot 2.2.1
I tryed to do this
#Bean
public MongoClientSettings mongoSettings() {
return MongoClientSettings
.builder()
.retryWrites(Boolean.FALSE)
.build();
}
But not worked.
=================================================================================
Second Time
I connected to AWS DocumentDb with SSH Tunnel.
Started my application with these database configuration
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public class MongoAutoConfiguration {
private final MongoClientFactory factory;
private final MongoClientOptions options;
private MongoClient mongo;
public MongoAutoConfiguration(MongoProperties properties, ObjectProvider<MongoClientOptions> options, Environment environment) {
this.options = options.getIfAvailable();
if (StringUtils.isEmpty(properties.getUsername()) || StringUtils.isEmpty(properties.getPassword())) {
properties.setUsername(null);
properties.setPassword(null);
}
properties.setUri(createUri(properties));
this.factory = new MongoClientFactory(properties, environment);
}
private String createUri(MongoProperties properties) {
String uri = "mongodb://";
if (StringUtils.hasText(properties.getUsername()) && !StringUtils.isEmpty(properties.getPassword())) {
uri = uri + properties.getUsername() + ":" + new String(properties.getPassword()) + "#";
}
return uri + properties.getHost() + ":" + properties.getPort() + "/" + properties.getDatabase() + "?retryWrites=false";
}
#PreDestroy
public void close() {
if (this.mongo != null) {
this.mongo.close();
}
}
#Bean
public MongoClient mongo() {
this.mongo = this.factory.createMongoClient(this.options);
return this.mongo;
}
}
And localy it saves the data without error.
But, if I put my API update in AWS ECS, and try to save, got the same error.
=================================================================================
Dependencies
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>4.1.4</version>
</dependency>
When you construct your connection string, you can include the parameters for disabling retryable writes, by adding this to your connection URI:
?replicaSet=rs0&readPreference=primaryPreferred&retryWrites=false&maxIdleTimeMS=30000
Then use this when creating the database factory and mongo template (this example uses the Reactive database factory, but the principle is the same for the SimpleMongoClientDatabaseFactory:
#Bean
fun reactiveMongoDatabaseFactory(
#Value("\${spring.data.mongodb.uri}") uri: String,
#Value("\${mongodb.database-name}") database: String
): ReactiveMongoDatabaseFactory {
val parsedURI = URI(uri)
return SimpleReactiveMongoDatabaseFactory(MongoClients.create(uri), database)
}

Integrate Redis to JHipster CacheConfiguration error

I'm trying to integrate redis cache to JHipster generator following this pull request on Github: https://github.com/jhipster/generator-jhipster/pull/10057/commits/cd2f2865d35dfd77624dd3a38ed32822e895539d#
I receive this error while building my project:
[ERROR] symbol: method getRedis()
[ERROR] location: class io.github.jhipster.config.JHipsterProperties.Cache
[ERROR] ../config/CacheConfiguration.java:[61,139] cannot find symbol
The method getRedis() is undefined for the type JHipsterProperties.CacheJava(67108964)
Where is getRedis() defined?
CacheConfiguration method in CacheConfiguration.java:
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;
public CacheConfiguration(JHipsterProperties jHipsterProperties) {
MutableConfiguration<Object, Object> jcacheConfig = new MutableConfiguration<>();
Config config = new Config();
config.useSingleServer()
.setAddress(jHipsterProperties.getCache().getRedis().getServer())
.setSubscriptionConnectionMinimumIdleSize(1)
.setSubscriptionConnectionPoolSize(50)
.setConnectionMinimumIdleSize(24)
.setConnectionPoolSize(64)
.setDnsMonitoringInterval(5000)
.setIdleConnectionTimeout(10000)
.setConnectTimeout(10000)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500)
.setDatabase(0)
.setPassword(null)
.setSubscriptionsPerConnection(5)
.setClientName(null)
.setSslEnableEndpointIdentification(true)
.setSslProvider(SslProvider.JDK)
.setSslTruststore(null)
.setSslTruststorePassword(null)
.setSslKeystore(null)
.setSslKeystorePassword(null)
.setPingConnectionInterval(0)
.setKeepAlive(false)
.setTcpNoDelay(false);
jcacheConfig.setStatisticsEnabled(true);
jcacheConfig.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, jHipsterProperties.getCache().getRedis().getExpiration())));
jcacheConfiguration = RedissonConfiguration.fromInstance(Redisson.create(config), jcacheConfig);
}
Am I missing some dependencies for getRedis()?
Note: I left out this in build.gradle.ejs; would this be causing the problem?
<%_ if (cacheProvider === 'redis') { _%>
implementation "org.redisson:redisson"
<%_ if (enableHibernateCache) { _%>
implementation "org.hibernate:hibernate-jcache"
<%_ } _%>
<%_ } _%>
Solution?:
ApplicationProperties.java:
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
private final Redis redis = new Redis();
public Redis getRedis() {
return redis;
}
public static class Redis {
private String server = JHipsterDefaults.Cache.Redis.server;
private int expiration = JHipsterDefaults.Cache.Redis.expiration;
public String getServer() {
return server;
}
public void setServer(String server) {
this.server = server;
}
public int getExpiration() {
return expiration;
}
public void setExpiration(int expiration) {
this.expiration = expiration;
}
}
}
CacheConfiguration.java
<%_ if (cacheProvider === 'redis') { _%>
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;
public CacheConfiguration(JHipsterProperties jHipsterProperties, ApplicationProperties applicationProperties) {
MutableConfiguration<Object, Object> jcacheConfig = new MutableConfiguration<>();
Config config = new Config();
config.useSingleServer()
.setAddress(applicationProperties.getRedis().getServer());
.setSubscriptionConnectionMinimumIdleSize(1)
.setSubscriptionConnectionPoolSize(50)
.setConnectionMinimumIdleSize(24)
.setConnectionPoolSize(64)
.setDnsMonitoringInterval(5000)
.setIdleConnectionTimeout(10000)
.setConnectTimeout(10000)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500)
.setDatabase(0)
.setPassword(null)
.setSubscriptionsPerConnection(5)
.setClientName(null)
.setSslEnableEndpointIdentification(true)
.setSslProvider(SslProvider.JDK)
.setSslTruststore(null)
.setSslTruststorePassword(null)
.setSslKeystore(null)
.setSslKeystorePassword(null)
.setPingConnectionInterval(0)
.setKeepAlive(false)
.setTcpNoDelay(false);
jcacheConfig.setStatisticsEnabled(true);
jcacheConfig.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, applicationProperties.getRedis().getExpiration())));
jcacheConfiguration = RedissonConfiguration.fromInstance(Redisson.create(config), jcacheConfig);
}
application.yml.ejs
# ===================================================================
# Application specific properties
# Add your own application properties here, see the ApplicationProperties class
# to have type-safe configuration, like in the JHipsterProperties above
#
# More documentation is available at:
# https://www.jhipster.tech/common-application-properties/
# ===================================================================
# application:
application.redis.server: redis://localhost:6379
application.redis.expiration: 300
You are missing the respective changes in the JHipster library which are not released yet (located in this pull request).
My advice (until it's released) would be to copy the changes (the Redis class and values) from JhipsterProperties.java to your ApplicationProperties.java.
Then if you need to configure the values to a non-default value, you can do so in your application.yml under the application: key.
Lastly add ApplicationProperties applicationProperties to the constructor in CacheConfiguration.java next to JhipsterProperties and reference getRedis() from there.
I believe the reddison dependency is also needed.

Validate schema programmatically using hibernate

In mose projects the way to run your java app with schema validation is with that configuration (when using spring):
spring.jpa.hibernate.ddl-auto=validate
I ran into a problem that I need to validate my schema at a specific times during running, is there any way to implement that?
I saw that hibernate managed it with the AbstractSchemaValidator,
I'm using spring with hibernate, and I didn't found any information how to deal with it, the only thing I found is How to validate database schema programmatically in hibernate with annotations?
, but it was removed in the older versions of spring-boot
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
any ideas?
This is solution, if your use case requires:
granular & explicit control of which part of the schema should be
validated
the need is to validate multiple schemas
the need is to validate schema that is not used by the service, on which scheduled validator is running
db connections used by application should not be influenced by validation in any way (meaning, you don't want to borrow connection from main connections pool)
If above applies for your needs, than this is example of how to do scheduled schema validation:
Sources
#SpringBootApplication
#EnableScheduling
#EnableConfigurationProperties(ScheamValidatorProperties.class)
public class SchemaValidatorApplication {
public static void main(String[] args) {
SpringApplication.run(SchemaValidatorApplication.class, args);
}
}
#ConfigurationProperties("schema-validator")
class ScheamValidatorProperties {
public Map<String, String> settings = new HashMap<>();
public ScheamValidatorProperties() {
}
public Map<String, String> getSettings() {
return this.settings;
}
public void setSome(Map<String, String> settings) {
this.settings = settings;
}
}
#Component
class ScheduledSchemaValidator {
private ScheamValidatorProperties props;
public ScheduledSchemaValidator(ScheamValidatorProperties props) {
this.props = props;
}
#Scheduled(cron = "0 0/1 * * * ?")
public void validateSchema() {
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(props.getSettings())
.build();
Metadata metadata = new MetadataSources(serviceRegistry)
.addAnnotatedClass(Entity1.class)
.addAnnotatedClass(Entity2.class)
.buildMetadata();
try {
new SchemaValidator().validate(metadata, serviceRegistry);
} catch (Exception e) {
System.out.println("Validation failed: " + e.getMessage());
} finally {
StandardServiceRegistryBuilder.destroy(serviceRegistry);
}
}
}
#Entity
#Table(name = "table1")
class Entity1 {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Entity1() {}
public Long getId() {
return id;
}
}
#Entity
#Table(name = "table2")
class Entity2 {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Entity2() {}
public Long getId() {
return id;
}
}
schema.sql
CREATE DATABASE IF NOT EXISTS testdb;
CREATE TABLE IF NOT EXISTS `table1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `table2` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
application.yml
spring:
cache:
type: none
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3309/testdb?useSSL=false&nullNamePatternMatchesAll=true&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: test_user
password: test_password
testWhileIdle: true
validationQuery: SELECT 1
jpa:
show-sql: false
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: none
naming:
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
properties:
hibernate.dialect: org.hibernate.dialect.MySQL8Dialect
hibernate.cache.use_second_level_cache: false
hibernate.cache.use_query_cache: false
hibernate.generate_statistics: false
hibernate.hbm2ddl.auto: validate
schema-validator:
settings:
connection.driver_class: com.mysql.cj.jdbc.Driver
hibernate.dialect: org.hibernate.dialect.MySQL8Dialect
hibernate.connection.url: jdbc:mysql://localhost:3309/testdb?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
hibernate.connection.username: test_user
hibernate.connection.password: test_password
hibernate.default_schema: testdb
docker-compose.yml
version: '3.0'
services:
db:
image: mysql:8.0.14
restart: always
ports:
- 3309:3306
environment:
MYSQL_ROOT_PASSWORD: test_password
MYSQL_DATABASE: testdb
MYSQL_USER: test_user
MYSQL_PASSWORD: test_password
If you want to let the SchemaValidator to reuse the connection configuration and the mapping information that are already configured in the project rather then defining them once again for schema validation, you should consider my solution such that you are DRY and don't need to maintain these configurations in two separate places.
Actually , what SchemaValidator requires is the Metadata instance which is only available during bootstrapping Hibernate . But we can use Hibernate Integrator API (as described in here) to capture it such that we can validate them later.
(1) Create SchemaValidateService which implements Hibernate Integrator API to capture Metadata. Also setup a #Scheduled method to validate the schema at desired time.
#Component
public class SchemaValidateService implements Integrator {
private Metadata metadata;
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
this.metadata = metadata;
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
}
//Adjust the scheduled time here
#Scheduled(cron = "0 0/1 * * * ?")
public void validate() {
try {
System.out.println("Start validating schema");
new SchemaValidator().validate(metadata);
} catch (Exception e) {
//log the validation error here.
}
System.out.println("Finish validating schema....");
}
}
(2) Register SchemaValidateService to Hibernate
#SpringBootApplication
#EnableScheduling
public class App {
#Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(SchemaValidateService schemaValidateService) {
return (prop -> {
List<Integrator> integrators = new ArrayList<>();
integrators.add(schemaValidateService);
prop.put("hibernate.integrator_provider", (IntegratorProvider) () -> integrators);
});
}
}
Also, this solution should has better performance as it does not need to create a new database connection for validating schema each time as it can just grab the connection from the existing connection pool.

Categories