I have an application running in production and we use postgres DB for storage. In prod we have read replica for the same DB and i wanted to setup multiple datasources using Jooq and SpringBoot So that I can re-direct the GET APIs to go and read from read replica Datasource and writes will happen to the primary DB.
I ma not sure how Jooq will be configured in this case. Do you have any sample which i can refer to work on this ?
As of now we are using application.properties to configure the datasource.
server.port=8080
management.endpoints.web.exposure.include=info,health,env
# To check why is the healthcheck failing
management.endpoint.health.show-details=always
info.app.name=deck-service
info.app.description=Deck REST services
info.app.version=0.1
spring.mvc.message-codes-resolver.format=PREFIX_ERROR_CODE
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jooq.sql-dialect=Postgres
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.pool-name=DeckServiceDB
spring.datasource.hikari.initial-size=5
spring.datasource.hikari.maximum-pool-size=40
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.idle-timeout=10000
spring.datasource.query-timeout=${TIMEOUT_FROM_AWS}spring.datasource.url=${DB_URL_FROM_AWS}
spring.datasource.username=${USERNAME_FROM_AWS}
spring.datasource.password=${PASSWORD_FROM_AWS}
#Optional properties
#spring.datasource.hikari.maxLifetime=2000000
#spring.datasource.hikari.leak-detection-threshold=60000
# ref - http://assets.en.oreilly.com/1/event/21/Connector_J%20Performance%20Gems%20Presentation.pdf
# ref - http://dev.mysql.com/doc/connector-j/5.1/en/connector-j-useconfigs.html
spring.datasource.prepStmtCacheSize=250
spring.datasource.prepStmtCacheSqlLimit=2048
spring.output.ansi.enabled=ALWAYS
# ref - https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-jmx.html
management.endpoints.jmx.exposure.exclude=*
management.endpoint.jolokia.enabled=false
#Writes LocalDateTime in ISO8601DateFormat if set to false. Default value set by Spring-boot is true
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=true
#tracing related properties
method.tracing.enabled=false
Our Repository is like this:
package com.chegg.deck.service.readds.dao;
import com.chegg.deck.service.PostgresJSONBBinding;
import com.chegg.deck.service.dao.BaseRepository;
import com.chegg.deck.service.model.Card;
import com.chegg.deck.service.service.util.JooqUtil;
import com.chegg.deck_service.db.tables.records.CardRecord;
import com.fasterxml.jackson.databind.JsonNode;
import graphql.GraphQLException;
import lombok.extern.slf4j.Slf4j;
import org.jooq.*;
import org.simpleflatmapper.converter.ContextualConverter;
import org.simpleflatmapper.jdbc.JdbcMapper;
import org.simpleflatmapper.jdbc.JdbcMapperFactory;
import org.simpleflatmapper.map.property.ConverterProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static com.chegg.deck_service.db.Tables.*;
/**
* CardRepository
* <p>
* Created User : dpatra
* Created On : 2019-01-23
* Updated Users : dpatra
* Updated On : 2019-01-23
*/
@Slf4j
@Repository
@Transactional
public class CardRepository{
static final Field[] CARD_DECK_FIELDS = new Field[] {CARD_DECK.ID, CARD.FOREIGN_ID, CARD_DECK.CARD_ID, CARD_DECK.DECK_ID, CARD.CARD_CONTENT, CARD.CREATED, CARD.UPDATED, CARD.ORIGINAL_CREATED, CARD.ORIGINAL_UPDATED, CARD_DECK.POSITION};
static final Field[] CARD_FIELDS = new Field[] {CARD.ID,CARD.FOREIGN_ID, CARD.CARD_CONTENT, CARD.CREATED, CARD.UPDATED, CARD.ORIGINAL_CREATED, CARD.ORIGINAL_UPDATED};
public static final ContextualConverter<Object, JsonNode> toJsonNode = (in, context) -> PostgresJSONBBinding.parse("" + in);
private static final JdbcMapper<Card> cardMapper = JdbcMapperFactory
.newInstance()
.addColumnProperty(CARD.CARD_CONTENT.getName(), ConverterProperty.of(toJsonNode))
.newMapper(Card.class);
@Autowired
protected DSLContext dsl;
@PostConstruct
public void init(){
dsl.configuration().settings().setQueryTimeout(querytimeout);
}
public DSLContext getDsl() {
return this.dsl;
}
public void setDsl(DSLContext dsl) {
this.dsl = dsl;
}
@Autowired
private CardDeckRepository cardDeckRepository;
/**
* Get cards for a given deck by the given deckId.
*
* @param deckId
* @param limit
* number of records to be fetched.
* @param offset
* after which record data will be fetched.
* @return cards list.
*/
public List<Card> getCardsByDeckId(UUID deckId, int limit, Float offset) {
SelectConditionStep<Record> conditionStep = formSelectCondition(deckId);
//Add offset value if its present.
if (null != offset && offset.floatValue() != 0) {
conditionStep.and(CARD_DECK.POSITION.greaterThan(offset.floatValue()));
}
ResultSet rs = conditionStep.orderBy(CARD_DECK.DECK_ID, CARD_DECK.POSITION).limit(limit).fetchResultSet();
return transformQueryResultIntoListOfCards(rs);
}
/**
* Pull the cards for the given deckId & set of card_deck ids.
*
* @param deckId
* @param ids
* @return List<Card>
*/
public List<Card> getCardsByDeckIdAndCardDeckIds(UUID deckId, List<UUID> ids) {
ResultSet rs = dsl.select(JooqUtil.fieldsWithAliases(CARD_DECK_FIELDS, Card.getAliases()))
.from(CARD_DECK)
.join(CARD).on(CARD.ID.eq(CARD_DECK.CARD_ID))
.join(DECK).on(DECK.ID.eq(CARD_DECK.DECK_ID))
.where(CARD_DECK.ID.in(ids).and(CARD_DECK.DECK_ID.eq(deckId)))
.orderBy(CARD_DECK.DECK_ID, CARD_DECK.POSITION)
.fetchResultSet();
return transformQueryResultIntoListOfCards(rs);
}
private Card transformQueryResultIntoCard(ResultSet rs){
try {
Iterator<Card> cards = cardMapper.iterator(rs);
if (!cards.hasNext()) {
return null;
}
Card found = cards.next();
if (cards.hasNext()) {
throw new GraphQLException("Multiple cards were found");
}
return found;
}
catch (Exception ex) {
throw new GraphQLException(ex.getMessage());
}
}
private List<Card> transformQueryResultIntoListOfCards(ResultSet rs){
try{
return cardMapper.stream(rs).collect(Collectors.toList());
} catch(Exception ex){
throw new GraphQLException(ex.getMessage());
}
}
}
Please let me know if I will need to read data from one datasource and write into another datasource, then how will I change my code and how to setup in SpringBoot application with Jooq.