28

私の問題は次のとおりです。Web システムでデータベース接続へのシングル ポイントとして機能するクラスが必要です。1 人のユーザーが 2 つの接続を開いているのを避けるためです。可能な限り最適化する必要があり、システム内のすべてのトランザクションを管理する必要があります。つまり、そのクラスのみが DAO をインスタンス化できる必要があります。さらに改善するには、接続プーリングも使用する必要があります。私は何をすべきか?

4

2 に答える 2

94

DAO Managerを実装する必要があります。このウェブサイトから主なアイデアを取り入れましたが、いくつかの問題を解決する独自の実装を作成しました。

ステップ 1: 接続プーリング

まず、接続プールを構成する必要があります。接続プールは、まあ、接続のプールです。アプリケーションが実行されると、接続プールは一定量の接続を開始します。これは、コストのかかる操作であるため、実行時に接続が作成されるのを避けるために行われます。このガイドは、構成方法を説明するものではありませんので、それについて調べてください。

記録として、 Javaを言語として、 Glassfishをサーバーとして使用します。

ステップ 2: データベースに接続する

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();

オブジェクト内のデータベースへの接続を開いたり閉じたりする必要があります。

STEP3:ワンポイントに!

さて、これをやったらどうなるでしょうか?

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;
        }        

    }

}

アプリケーションが起動すると、だれかがシングルトンを必要とするたびに、システムがシングルトンをインスタンス化しますDAOManager。これで、単一のアクセス ポイントが作成されました。

しかし、理由により、シングルトンはアンチパターンです! シングルトンが嫌いな人がいることは知っています。しかし、それは問題を解決します(そして私のものを解決しました)。これは、このソリューションを実装する方法にすぎません。他の方法がある場合は、提案してください。

ステップ 4: しかし、何か問題があります...

はい、確かにあります。シングルトンは、アプリケーション全体に対して 1 つのインスタンスのみを作成します! これは多くのレベルで間違っています。特に、アプリケーションがマルチスレッド化される Web システムがある場合はなおさらです。では、これをどのように解決しますか?

Java は という名前のクラスを提供しますThreadLocalThreadLocal変数は、スレッドごとに 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 つのインスタンスを持つことができるようになりました。

ステップ 5: DAO を作成する

ADAOManagerは DAO なしでは意味がありません。したがって、少なくともいくつか作成する必要があります。

「Data Access Object」の略である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 があるとしますFirstSecond両方とも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"ます。

ステップ 6: マネージャーをファクトリーにする

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.");
    }

}

ステップ 7: すべてをまとめる

これで準備完了です。次のコードを試してください。

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();

おしゃれで読みやすいじゃないですか。それだけでなく、 を呼び出すと、DAO が使用しているすべての接続close()が閉じられます。しかし、どうやって?まあ、同じ接続を共有しているので、当然のことです。

ステップ 8: クラスの微調整

ここからいくつかのことができます。接続が閉じられてプールに戻されるようにするには、 で次の手順を実行しますDAOManager

@Override
protected void finalize()
{

    try{ this.close(); }
    finally{ super.finalize(); }

}

をカプセル化するメソッドを実装することもsetAutoCommit()できるため、トランザクションをより適切に処理できcommit()ますrollback()Connection私がしたことは、 を保持するだけでなく、ConnectionDAOManagerも保持するPreparedStatementことResultSetです。したがって、それを呼び出すclose()と、両方も閉じます。ステートメントと結果セットをすばやく閉じる方法です。

このガイドがあなたの次のプロジェクトで役立つことを願っています!

于 2012-10-10T05:02:37.490 に答える
7

単純な 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();

リソースで try を使用していることに注意してください。このコードは、接続リークに対して安全であり、クリーンで、簡単です。多くの場合、ファクトリ、インターフェイス、およびすべての配管で完全な DAO パターンに従いたくないでしょう。真の価値を追加します。

ThreadLocalsを使用するのは良い考えだとは思いません。受け入れられた回答のように使用されるのは、クラスローダーリークの原因です

try finally ブロックまたはリソースでの try の使用で、常にリソース (ステートメント、ResultSet、接続) を閉じることを忘れないでください

于 2014-01-20T00:50:10.220 に答える