एक उदाहरण यहां पाया जा सकता है:https://github.com/afedulov/routing-data- स्रोत ।
स्प्रिंग डेटा स्रोत की विविधता प्रदान करता है, जिसे AbstractRoutingDatasource
. कहा जाता है . इसका उपयोग मानक डेटा स्रोत कार्यान्वयन के स्थान पर किया जा सकता है और एक तंत्र को यह निर्धारित करने में सक्षम बनाता है कि रनटाइम पर प्रत्येक ऑपरेशन के लिए कौन सा ठोस डेटा स्रोत उपयोग करना है। आपको बस इसे विस्तारित करना है और एक सार determineCurrentLookupKey
का कार्यान्वयन प्रदान करना है। तरीका। यह ठोस डेटा स्रोत निर्धारित करने के लिए आपके कस्टम तर्क को लागू करने का स्थान है। लौटाई गई वस्तु लुकअप कुंजी के रूप में कार्य करती है। यह आमतौर पर एक स्ट्रिंग या एन एनम होता है, जिसका उपयोग स्प्रिंग कॉन्फ़िगरेशन में क्वालीफायर के रूप में किया जाता है (विवरण का पालन किया जाएगा)।
package website.fedulov.routing.RoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
आप सोच रहे होंगे कि वह DbContextHolder ऑब्जेक्ट क्या है और यह कैसे पता चलता है कि कौन सा डेटा स्रोत पहचानकर्ता वापस लौटना है? ध्यान रखें कि determineCurrentLookupKey
जब भी लेनदेन प्रबंधक कनेक्शन का अनुरोध करता है तो विधि को कॉल किया जाएगा। यह याद रखना महत्वपूर्ण है कि प्रत्येक लेनदेन एक अलग धागे से "संबद्ध" होता है। अधिक सटीक रूप से, TransactionsManager कनेक्शन को वर्तमान थ्रेड से बांधता है। इसलिए अलग-अलग लक्ष्य डेटा स्रोतों को अलग-अलग लेन-देन भेजने के लिए हमें यह सुनिश्चित करना होगा कि प्रत्येक थ्रेड विश्वसनीय रूप से पहचान सके कि कौन सा डेटा स्रोत इसके उपयोग के लिए नियत है। यह विशिष्ट डेटा स्रोत को एक थ्रेड और इसलिए एक लेनदेन के लिए बाध्य करने के लिए थ्रेडलोकल चर का उपयोग करना स्वाभाविक बनाता है। यह इस प्रकार किया जाता है:
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();
}
}
जैसा कि आप देखते हैं, आप एक एनम को कुंजी के रूप में भी उपयोग कर सकते हैं और स्प्रिंग नाम के आधार पर इसे सही ढंग से हल करने का ध्यान रखेगा। संबद्ध डेटा स्रोत कॉन्फ़िगरेशन और कुंजियाँ इस तरह दिख सकती हैं:
....
<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>
इस समय आप स्वयं को कुछ ऐसा करते हुए पा सकते हैं:
@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
अब हम नियंत्रित कर सकते हैं कि कौन से डेटा स्रोत का उपयोग किया जाएगा और अनुरोध को अग्रेषित करें जैसा हम चाहते हैं। अछा लगता है!
...या करता है? सबसे पहले, वे स्थिर विधि एक जादुई DbContextHolder को कॉल करती है जो वास्तव में चिपक जाती है। ऐसा लगता है कि वे व्यावसायिक तर्क से संबंधित नहीं हैं। और वे नहीं करते हैं। न केवल वे उद्देश्य को संप्रेषित नहीं करते हैं, बल्कि वे नाजुक और त्रुटि-प्रवण लगते हैं (कैसे dbType को साफ करना भूल जाते हैं)। और क्या होगा यदि setDbType और cleanDbType के बीच एक अपवाद फेंक दिया गया हो? हम इसे यूं ही नजरअंदाज नहीं कर सकते। हमें पूरी तरह से सुनिश्चित होना चाहिए कि हम डीबी टाइप को रीसेट कर दें, अन्यथा थ्रेडपूल पर लौटा थ्रेड "टूटी हुई" स्थिति में हो सकता है, अगली कॉल में प्रतिकृति को लिखने का प्रयास कर रहा है। तो हमें इसकी आवश्यकता है:
@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());
}
ओह >_<
! यह निश्चित रूप से ऐसा कुछ नहीं दिखता है जिसे मैं केवल पढ़ने के लिए प्रत्येक विधि में रखना चाहता हूं। क्या हम बेहतर कर सकते हैं? बेशक! "एक विधि की शुरुआत में कुछ करें, फिर अंत में कुछ करें" का यह पैटर्न घंटी बजना चाहिए। बचाव के पहलू!
दुर्भाग्य से यह पोस्ट कस्टम पहलुओं के विषय को कवर करने के लिए पहले ही बहुत लंबा हो गया है। आप इस का उपयोग करके पहलुओं का उपयोग करने के विवरण पर अनुवर्ती कार्रवाई कर सकते हैं। लिंक ।