私の問題は次のとおりです。 Webシステムのデータベース接続への単一ポイントとして機能するクラスが必要です。そのため、1人のユーザーが2つのオープン接続を使用するのを避けます。可能な限り最適である必要があり、システム内のすべてのトランザクションを管理する必要があります。言い換えれば、そのクラスのみがDAOをインスタンス化できるはずです。それを改善するには、接続プーリングも使用する必要があります!私は何をすべきか?
DAOマネージャーを実装する必要があります。 このWebサイト から主なアイデアを取りましたが、いくつかの問題を解決する独自の実装を作成しました。
まず、接続プールを構成する必要があります。接続プールは、接続のプールです。アプリケーションが実行されると、接続プールは一定量の接続を開始します。これは、高価な操作であるため、実行時に接続が作成されないようにするためです。このガイドは設定方法を説明するものではないので、それについて見て回ってください。
記録のために、言語としてJavaとGlassfishを使用します私のサーバーとして。
DAOManager
クラスを作成することから始めましょう。実行時に接続を開いたり閉じたりするメソッドを与えましょう。派手なものは何もありません。
_public class DAOManager {
public DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL"); //The string should be the same name you're giving to your JNDI in Glassfish.
}
catch(Exception e) { throw e; }
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
}
_
これはそれほど派手なクラスではありませんが、これから行うことの基礎になります。したがって、これを行う:
_DAOManager mngr = new DAOManager();
mngr.open();
mngr.close();
_
オブジェクト内のデータベースへの接続を開いたり閉じたりする必要があります。
今、もしこれをやったら?
_DAOManager mngr1 = new DAOManager();
DAOManager mngr2 = new DAOManager();
mngr1.open();
mngr2.open();
_
"世界でこれを行う理由は何ですか?"。しかし、その後、プログラマーが何をするのか決してわかりません。それでも、プログラマは新しい接続を開く前に接続を閉じることを偽造する場合があります。さらに、これはアプリケーションのリソースの無駄です。 実際に2つ以上の接続をオープンする場合は、ここで停止します。これは、ユーザーごとに1つの接続の実装になります。
単一のポイントにするために、このクラスをsingletonに変換する必要があります。シングルトンは、特定のオブジェクトのインスタンスを1つだけ持つことができるデザインパターンです。それで、シングルトンにしましょう!
public
コンストラクターをプライベートコンストラクターに変換する必要があります。インスタンスを呼び出す人にのみインスタンスを与える必要があります。 DAOManager
はファクトリーになります!private
クラスを追加する必要があります。getInstance()
メソッドも必要です。実装方法を見てみましょう。
_public class DAOManager {
public static DAOManager getInstance() {
return DAOManagerSingleton.INSTANCE;
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
private DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL");
}
catch(Exception e) { throw e; }
}
private static class DAOManagerSingleton {
public static final DAOManager INSTANCE;
static
{
DAOManager dm;
try
{
dm = new DAOManager();
}
catch(Exception e)
dm = null;
INSTANCE = dm;
}
}
}
_
アプリケーションの起動時に、誰かがシングルトンを必要とするたびに、システムは1つのDAOManager
をインスタンス化します。非常にきちんとした、単一のアクセスポイントを作成しました!
しかし、シングルトンは理由が理由でアンチパターンです!私は、シングルトンを嫌う人がいることを知っています。しかし、それは問題をかなりうまく解決します(そして私のものを解決しました)。これは、このソリューションを実装する方法の1つにすぎません。他の方法があれば歓迎します。
はい、確かにあります。 シングルトンは、アプリケーション全体に対して1つのインスタンスのみを作成します!そして、これは多くのレベルで間違っています。 multithreaded!では、これをどうやって解決するのでしょうか?
JavaはThreadLocal
という名前のクラスを提供します。 ThreadLocal
変数には、スレッドごとに1つのインスタンスがあります。ねえ、それは私たちの問題を解決します! 仕組みの詳細を参照 、続行するには、その目的を理解する必要があります。
INSTANCE
ThreadLocal
を作成しましょう。この方法でクラスを変更します。
_public class DAOManager {
public static DAOManager getInstance() {
return DAOManagerSingleton.INSTANCE.get();
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
private DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL");
}
catch(Exception e) { throw e; }
}
private static class DAOManagerSingleton {
public static final ThreadLocal<DAOManager> INSTANCE;
static
{
ThreadLocal<DAOManager> dm;
try
{
dm = new ThreadLocal<DAOManager>(){
@Override
protected DAOManager initialValue() {
try
{
return new DAOManager();
}
catch(Exception e)
{
return null;
}
}
};
}
catch(Exception e)
dm = null;
INSTANCE = dm;
}
}
}
_
私は真剣にこれをしないのが大好きだ
_catch(Exception e)
{
return null;
}
_
ただし、initialValue()
は例外をスローできません。ああ、initialValue()
ということですか?このメソッドは、ThreadLocal
変数が保持する値を示します。基本的には初期化しています。これにより、スレッドごとに1つのインスタンスを作成できるようになりました。
DAOManager
はDAOなしでは何もありません。したがって、少なくともそれらをいくつか作成する必要があります。
「データアクセスオブジェクト」の略であるDAOは、特定のテーブルを表すクラスにデータベース操作を管理する責任を与える設計パターンです。
DAOManager
をより効率的に使用するために、GenericDAO
を定義します。これは、すべてのDAO間で共通の操作を保持する抽象DAOです。
_public abstract class GenericDAO<T> {
public abstract int count() throws SQLException;
//Protected
protected final String tableName;
protected Connection con;
protected GenericDAO(Connection con, String tableName) {
this.tableName = tableName;
this.con = con;
}
}
_
今のところ、それで十分でしょう。いくつかのDAOを作成しましょう。 2つのPOJO、First
とSecond
があり、両方ともString
という名前のdata
フィールドとそのゲッターとセッターがあるとします。
_public class FirstDAO extends GenericDAO<First> {
public FirstDAO(Connection con) {
super(con, TABLENAME);
}
@Override
public int count() throws SQLException {
String query = "SELECT COUNT(*) AS count FROM "+this.tableName;
PreparedStatement counter;
try
{
counter = this.con.PrepareStatement(query);
ResultSet res = counter.executeQuery();
res.next();
return res.getInt("count");
}
catch(SQLException e){ throw e; }
}
//Private
private final static String TABLENAME = "FIRST";
}
_
SecondDAO
はほぼ同じ構造になり、TABLENAME
を_"SECOND"
_に変更するだけです。
DAOManager
は、単一の接続ポイントとして機能するだけではありません。実際、DAOManager
はこの質問に答える必要があります。
データベースへの接続を管理するのは誰ですか?
個々のDAOはそれらを管理すべきではありませんが、DAOManager
。質問には部分的に回答しましたが、DAOさえも含めて、データベースへの他の接続を誰にも管理させないでください。しかし、DAOにはデータベースへの接続が必要です!誰がそれを提供すべきですか? DAOManager
確かに! DAOManager
内にファクトリメソッドを作成する必要があります。それだけでなく、DAOManager
も現在の接続を渡します!
Factoryは、返される子クラスを正確に知らなくても、特定のスーパークラスのインスタンスを作成できるデザインパターンです。
まず、テーブルをリストするenum
を作成しましょう。
_public enum Table { FIRST, SECOND }
_
そして今、DAOManager
内のファクトリメソッド:
_public GenericDAO getDAO(Table t) throws SQLException
{
try
{
if(this.con == null || this.con.isClosed()) //Let's ensure our connection is open
this.open();
}
catch(SQLException e){ throw e; }
switch(t)
{
case FIRST:
return new FirstDAO(this.con);
case SECOND:
return new SecondDAO(this.con);
default:
throw new SQLException("Trying to link to an unexistant table.");
}
}
_
私たちは今行ってもいいです。次のコードを試してください:
_DAOManager dao = DAOManager.getInstance();
FirstDAO fDao = (FirstDAO)dao.getDAO(Table.FIRST);
SecondDAO sDao = (SecondDAO)dao.getDAO(Table.SECOND);
System.out.println(fDao.count());
System.out.println(sDao.count());
dao.close();
_
おしゃれで読みやすいのではないでしょうか?それだけでなく、close()
を呼び出すと、すべての単一接続 DAOが使用しています。 しかし、どうやって?!彼らは同じ接続を共有しているので、それは自然なことです。
これからいくつかのことができます。接続が閉じられてプールに戻されるようにするには、DAOManager
で次の手順を実行します。
_@Override
protected void finalize()
{
try{ this.close(); }
finally{ super.finalize(); }
}
_
Connection
からsetAutoCommit()
、commit()
およびrollback()
をカプセル化するメソッドを実装して、トランザクションをより適切に処理することもできます。また、Connection
を保持するだけでなく、DAOManager
もPreparedStatement
とResultSet
を保持しています。したがって、close()
を呼び出すと、両方も閉じます。ステートメントと結果セットをすばやく閉じる方法!
このガイドが次のプロジェクトで役立つことを願っています!
プレーンJDBCで単純なDAOパターンを実行したい場合は、シンプルに保つ必要があると思います。
public List<Customer> listCustomers() {
List<Customer> list = new ArrayList<>();
try (Connection conn = getConnection();
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery("select * from customers")) {
while (rs.next()) {
list.add(processRow(rs));
}
return list;
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e); //or your exceptions
}
}
たとえば、CustomersDaoまたはCustomerManagerと呼ばれるクラスでこのパターンに従うことができ、シンプルで呼び出すことができます
CustomersDao dao = new CustomersDao();
List<Customers> customers = dao.listCustomers();
私はリソースを試してみており、このコードは接続リークに対して安全で、クリーンで簡単であることに注意してください実際の価値を追加します。
ThreadLocalsを使用するのは良い考えだとは思わない、受け入れられた答えのように使用されるBadはクラスローダーリークの原因です
リソース(ステートメント、ResultSets、接続)を常にtry finallyブロックで閉じるか、try with resourcesを使用してください