Een voorbeeld is hier te vinden:https://github.com/afedulov/routing-data- bron .
Spring biedt een variant van DataSource, genaamd AbstractRoutingDatasource
. Het kan worden gebruikt in plaats van standaard DataSource-implementaties en maakt een mechanisme mogelijk om tijdens runtime te bepalen welke concrete DataSource voor elke bewerking moet worden gebruikt. Het enige wat u hoeft te doen is het uit te breiden en een implementatie van een abstracte determineCurrentLookupKey
te geven methode. Dit is de plek om uw aangepaste logica te implementeren om de concrete DataSource te bepalen. Het geretourneerde object dient als een opzoeksleutel. Het is meestal een String of en Enum, gebruikt als een kwalificatie in de Spring-configuratie (details volgen).
package website.fedulov.routing.RoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
U vraagt zich misschien af wat dat DbContextHolder-object is en hoe het weet welke DataSource-ID moet worden geretourneerd? Houd er rekening mee dat determineCurrentLookupKey
methode wordt aangeroepen wanneer TransactionsManager een verbinding aanvraagt. Het is belangrijk om te onthouden dat elke transactie is "geassocieerd" met een aparte thread. Om precies te zijn, TransactionsManager bindt Connection aan de huidige thread. Daarom moeten we, om verschillende transacties naar verschillende doelgegevensbronnen te verzenden, ervoor zorgen dat elke thread op betrouwbare wijze kan identificeren welke gegevensbron bestemd is om te worden gebruikt. Dit maakt het natuurlijk om ThreadLocal-variabelen te gebruiken voor het binden van specifieke DataSource aan een Thread en dus aan een Transactie. Dit is hoe het wordt gedaan:
public enum DbType {
MASTER,
REPLICA1,
}
public class DbContextHolder {
private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
public static void setDbType(DbType dbType) {
if(dbType == null){
throw new NullPointerException();
}
contextHolder.set(dbType);
}
public static DbType getDbType() {
return (DbType) contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
Zoals je ziet, kun je ook een enum als sleutel gebruiken en Spring zal ervoor zorgen dat het correct wordt opgelost op basis van de naam. Bijbehorende DataSource-configuratie en sleutels kunnen er als volgt uitzien:
....
<bean id="dataSource" class="website.fedulov.routing.RoutingDataSource">
<property name="targetDataSources">
<map key-type="com.sabienzia.routing.DbType">
<entry key="MASTER" value-ref="dataSourceMaster"/>
<entry key="REPLICA1" value-ref="dataSourceReplica"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceMaster"/>
</bean>
<bean id="dataSourceMaster" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="${db.master.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<bean id="dataSourceReplica" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="${db.replica.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
Op dit punt zou je kunnen merken dat je zoiets als dit doet:
@Service
public class BookService {
private final BookRepository bookRepository;
private final Mapper mapper;
@Inject
public BookService(BookRepository bookRepository, Mapper mapper) {
this.bookRepository = bookRepository;
this.mapper = mapper;
}
@Transactional(readOnly = true)
public Page<BookDTO> getBooks(Pageable p) {
DbContextHolder.setDbType(DbType.REPLICA1); // <----- set ThreadLocal DataSource lookup key
// all connection from here will go to REPLICA1
Page<Book> booksPage = callActionRepo.findAll(p);
List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
DbContextHolder.clearDbType(); // <----- clear ThreadLocal setting
return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
}
...//other methods
Nu kunnen we bepalen welke DataSource wordt gebruikt en verzoeken doorsturen zoals we willen. Ziet er goed uit!
...Of toch? Allereerst vallen die statische methode-aanroepen naar een magische DbContextHolder echt op. Ze zien eruit alsof ze niet thuishoren in de bedrijfslogica. En dat doen ze niet. Ze geven niet alleen niet het doel aan, maar ze lijken ook kwetsbaar en foutgevoelig (wat dacht je van vergeten het dbType op te schonen). En wat als er een uitzondering wordt gegenereerd tussen setDbType en cleanDbType? We kunnen het niet zomaar negeren. We moeten er absoluut zeker van zijn dat we het dbType resetten, anders zou Thread die wordt teruggestuurd naar de ThreadPool in een "gebroken" staat kunnen zijn en bij de volgende aanroep naar een replica proberen te schrijven. Dus we hebben dit nodig:
@Transactional(readOnly = true)
public Page<BookDTO> getBooks(Pageable p) {
try{
DbContextHolder.setDbType(DbType.REPLICA1); // <----- set ThreadLocal DataSource lookup key
// all connection from here will go to REPLICA1
Page<Book> booksPage = callActionRepo.findAll(p);
List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
DbContextHolder.clearDbType(); // <----- clear ThreadLocal setting
} catch (Exception e){
throw new RuntimeException(e);
} finally {
DbContextHolder.clearDbType(); // <----- make sure ThreadLocal setting is cleared
}
return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
}
Yikes >_<
! Dit ziet er absoluut niet uit als iets dat ik in elke alleen-lezen-methode zou willen gebruiken. Kunnen we het beter doen? Natuurlijk! Dit patroon van "doe iets aan het begin van een methode, doe dan iets aan het einde" zou een belletje moeten doen rinkelen. Aspecten om te redden!
Helaas is dit bericht al te lang geworden om het onderwerp van aangepaste aspecten te behandelen. U kunt de details van het gebruik van aspecten opvolgen met behulp van deze link .