
ãå"Tsurugi" Tipséã⌀éã®ãã©ã³ã¶ã¯ã·ã§ã³ãæãå Žåã¯ã»ãã·ã§ã³ããŒã«ã®äœæãæ€èšãããããã°ã©ãã³ã°Tipsã
*****
ãã¡ãã®èšäºã¯ã
Tsurugi Advent Calendar 2024ïŒhttps://adventar.org/calendars/10853ïŒ
ã«åå ããŠããŸãã
å"Tsurugi"é¢é£ã®ãšã³ãžãã¢ã12æ25æ¥ãŸã§ã®æéãããŸããŸãªèšäºãæ²èŒããŠãããŸãã
æ¯éããã¡ããã芧ãã ããïŒ
*****
⌀éã®ãã©ã³ã¶ã¯ã·ã§ã³ãæããããªãªã³ã©ã€ã³Webã¢ããªã±ãŒã·ã§ã³ã®å Žåããã©ã³ã¶ã¯ã·ã§ã³ã®ãã³ã«DBãžã®æ¥ç¶ãâŸããšããã®ãªãŒããŒããããç¡èŠã§ããªããªãå¯èœæ§ããããŸãã
ãŸãDBãšããŠããã¯ã©ã€ã¢ã³ãããã®æ¥ç¶æ°ãç¡å¶éã«å¢ããŠããããšã¯é¿ããããã®ã§ãããã®ãããªã³ã©ã€ã³Webã¢ããªã±ãŒã·ã§ã³ã§ã¯ãã¢ããªã±ãŒã·ã§ã³ãµãŒããŒåŽã§DBãžã®æ¥ç¶ããŒã«ã✀æããŠãããã¢ããªã±ãŒã·ã§ã³ã¯ãã®æ¥ç¶ããŒã«ããDBæ¥ç¶ãååŸããŠåŠçãâŸãããšããã¹ã¿ã€ã«ãâŒè¬çã§ãã
Spring Framework ã Jakarta EE ã§ã¯ãæ¥ç¶ããŒã«ã管çããä»çµã¿ã✀æãããŠããŸãããããã¯JDBCãªã©ã®æ¢åAPIãåæãšããŠããŸãã
ããã§ã¯ Spring Framework ãäŸã«ãTsurugiã«å¯Ÿããæ¥ç¶ããŒã«ïŒTsurugiã®å Žåã¯ã»ãã·ã§ã³ããŒã«ïŒãæ§ç¯ããäŸã✰ããŸãã
ã»ãã·ã§ã³ããŒã«ã¯ Spring Bean ãšããŠäœããä»ã® Bean ã«ã€ã³ãžã§ã¯ãã§ããããã«ããŸãã
Jakarta EE ã®å Žåã¯ãSingleton EJB ããã㯠ApplicationScoped 㪠CDI Bean ãšããŠäœãã®ãããã§ãããã
import com.tsurugidb.iceaxe.TsurugiConnector;
import com.tsurugidb.iceaxe.session.TsurugiSession;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
@Component
public class TsurugiSessionPool {
// 1, 2
private final Deque<SessionCache> sessionCacheDeque = new
ConcurrentLinkedDeque<>();
private final TsurugiConnector connector;
public TsurugiSessionPool(TsurugiConnector connector) {
this.connector = connector;
}
// 3
public SessionCache getSessionCache() throws IOException {
while (true) {
var sessionCache = sessionCacheDeque.pollFirst();
// 4
if (sessionCache == null) {
var session = connector.createSession();
return new SessionCache(this, session);
}
var session = sessionCache.getSession();
// 5
if (session.isClosed() || !session.isAlive()) {
// ä¿æããŠããã»ãã·ã§ã³ãç¡å¹ãªå Žåã¯åãçŽã
continue;
}
return sessionCache;
}
}
// 6
void releaseSessionCache(SessionCache sessionCache) {
var session = sessionCache.getSession();
if (session.isClosed() || !session.isAlive()) {
// æ¥ç¶ãç¡å¹ãªå Žåã¯æ»ããã«ç Žæ£
return;
}
sessionCacheDeque.addFirst(sessionCache);
}
// 7
@PreDestroy
public void destroy() {
for (SessionCache sessionCache : sessionCacheDeque) {
try {
sessionCache.getSession().close();
} catch (IOException | InterruptedException ignore) {
// çµäºæåŠçãªã®ã§äŸå€ãçºâœ£ããŠãç¡èŠãã
}
}
sessionCacheDeque.clear();
}
}å®è£ äžã®ãã€ã³ãã¯æ¬¡ã®éãã§ãã
ã»ãã·ã§ã³ããŒã«ã¯ Deque ã§ç®¡çããŸããè€æ°ã¹ã¬ããããã¢ã¯ã»ã¹ãããã®ã§ConcurrentLinkedDeque ã䜿ããŸãã
ããã§ã¯å®¹éç¡å¶éã® ConcurrentLindedDeque ã䜿ã£ãŠããŸãããããŒã«ãµã€ãºãå¶éãããå Žå㯠LinkedBlockingDeque ã䜿ã£ãŠããããããããŸãããã»ãã·ã§ã³ãªããžã§ã¯ãã¯åŸè¿°ãã SessionCache ãšããã©ãããŒã¯ã©ã¹ã«ã©ããããŠç®¡çããŸãããã®ã¯ã©ã¹ã¯ãã»ãã·ã§ã³ãã¯ããŒãºãããéã«å®éã«éããã®ã§ã¯ãªããã»ãã·ã§ã³ããŒã«ã«è¿åŽãã圹å²ãæã¡ãŸãã
å©âœ€åŽã¯ getSessionCache() ã¡ãœãããçµç±ã㊠SessionCache ãªããžã§ã¯ããååŸããŸãã
ããŒã«ããã»ãã·ã§ã³ãååŸã§ããªãããããã¯ååŸããŠãæ¥ç¶ãç¡å¹ã§ãã£ãå Žåã¯æ°ããTsurugiConnector ãã TsurugiSession ãååŸã㊠SessionChache ã«ãããã§è¿ããŸãã
ãã£ãã·ã¥ããååŸããã»ãã·ã§ã³ãç¡å¹ã§ããå Žåã¯åååŸãâŸããŸã (ããã§åãåºãããç¡å¹ãªã»ãã·ã§ã³ã¯æšãŠãããããšã«ãªã) ã
ãã® releaseSessionCache() ã¡ãœããã¯åŸè¿°ã® SessionCache ããåŒã³åºããã䜿ãçµãã£ãã»ãã·ã§ã³ãããŒã«ã«æ»ããŸãã
@PreDestory ã¢ãããŒã·ã§ã³ãä»äžããã¡ãœãããå®è£ ãããã®Beanãç Žæ£ããã (ã¢ããªã±ãŒã·ã§ã³ãçµäºãã) ã¿ã€ãã³ã°ã§ã»ãã·ã§ã³ãå šãŠéããŸãã
SessionCache ã®å®è£ ã¯æ¬¡ã®ããã«ãªããŸãã
import com.tsurugidb.iceaxe.session.TsurugiSession;
import java.io.Closeable;
import java.io.IOException;
public class SessionCache implements Closeable { // 1
private final TsurugiSessionPool ownerPool;
private final TsurugiSession session;
public SessionCache(TsurugiSessionPool ownerPool, TsurugiSession session) {
this.ownerPool = ownerPool;
this.session = session;
}
// 2
public TsurugiSession getSession() {
return session;
}
@Override
public void close() throws IOException {
ownerPool.releaseSessionCache(this);
}
}å®è£ äžã®ãã€ã³ãã¯æ¬¡ã®éãã§ãã
java.io.Closeable ã€ã³ã¿ãŒãã§ãŒã¹ãå®è£ ããtry-with-resourcesâœã§æ±ããããã«ããŸãã
getSession() ã¡ãœãããéããŠâŸâŸãä¿æãã TsurugiSession ãªããžã§ã¯ããè¿ããŸãã
close() åŠçã«ãŠãå éšã§ SessionCache#releaseSessionCache() ãåŒã³åºããŠâŸâŸãããŒã«ã«è¿åŽããŸãã
ãã®ã»ãã·ã§ã³ããŒã«ãå©âœ€ããâœ
æ³ã✰ããŸãã
ã Webã¢ããªã±ãŒã·ã§ã³ã§ã¯ãã©ã³ã¶ã¯ã·ã§ã³å¢çãšãªãã³ã³ããŒãã³ãã§TransactionManagerã䜿ã ãã«ãŠäŸâœ°ãããµãŒãã¹ã³ã³ããŒãã³ãã«ã€ããŠããã®ã»ãã·ã§ã³ããŒã«ãå©âœ€ããâœ
æ³ã«æžãæãããã®ã«ãªããŸãã
@Service
public class ExampleService {
private final TsurugiSessionPool sessionPool;
private final ExampleRepository repository;
// 1
public ExampleService(TsurugiSessionPool sessionPool, ExampleRepository
repository) {
this.sessionPool = sessionPool;
this.repository = repository;
}
public ExampleResponse doSomeLogic(ExampleInput input) {
// 2
try (SessionCache sessionCache = sessionPool.getSessionCache()) {
// 3
var tm = sessionCache.getSession().createTransactionManager(
TgTmSetting.ofAlways(TgTxOption.ofOCC(), 2);
return tm.execute(transaction -> {
// 1ãã©ã³ã¶ã¯ã·ã§ã³ã«ãŸãšããã¹ãåŠçãå®è£
ããŠçµæãè¿ã
});
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}å®è£ äžã®ãã€ã³ãã¯æ¬¡ã®éãã§ãã
ã»ãã·ã§ã³ããŒã«ã¯Spring BeanãšããŠäœã£ãŠããã®ã§ãä»ã®Spring Beanã«ã€ã³ãžã§ã¯ãå¯èœã§ããããã§ã¯ã³ã³ã¹ãã©ã¯ã¿ã€ã³ãžã§ã¯ã·ã§ã³ã䜿ã£ãŠããŸãã
try-with-resourcesâœã䜿ã£ãŠ SessionCache ãªããžã§ã¯ããååŸããŠããŸããããããããšã§tryâœãæãããšâŸåçã« SesshonCache#close ãåŒã³åºãããããŒã«ã«ã»ãã·ã§ã³ãè¿åŽãããŸãã
SessionCache#getSession() ã¡ãœããã§ TsurugiSession ãååŸãTsurugiTransactionManagerãäœããŸãã
次äžä»£é«éRDBå"Tsurugi"ã¯ããªãŒãã³ãœãŒã¹ã§å
¬éäžã§ãã
ãã²ããŠã³ããŒãããŠã¿ãŠãè§Šã£ãŠã¿ãŠãã ããã
ããŠã³ããŒãã¯ã³ãã©ãã
https://www.tsurugidb.com/