TobiのSpring Chapter 7.3.3~7.5.2定理


リソースの抽象化


同じクラスパス以外のXMLまたは相対クラスパスではなく、サーバまたは開発システム内の特定のフォルダ内のファイルを読み込めませんか?Web上のリソースファイルをさらにインポートできますか?
  • 同じ目的では、様々な異なる使用方法の技術が存在する.
  • しげん

  • Springは、Javaに存在する不一致なリソースアクセスAPIを抽象化し、リソースインタフェースと呼ばれる抽象インタフェースを定義する.
  • サービス抽象オブジェクトとは異なり、空登録ではなく価格で扱われます.リソースは、OXMやトランザクションのようにサービスを提供するのではなく、単純な情報を持つ値として指定されます.
  • したがって,抽象化を適用する方法は問題である.Resourceが空に登録しないと言っている以上、<property>のvalue-atreviewに入れるには外部で指定するしかありません.しかしvalueを入れることができるのは簡単な文字列だけです.

    リソースローダ

  • 文字列として定義されたリソースを実際のリソースタイプに変換するResourceLoaderを提供します.
  • public interface ResourceLoader{
        // location에 담긴 스트링 정보를 바탕으로 그에 적절한 Resource로 반환해준다.
        Resource getResource(Stirng location);
    }
  • ResourceLoaderの典型的な例はSpringのアプリケーションコンテキストです.ApplicationContextはResourceLoaderを継承しています.
  • スプリング構成情報を含むxmlは、リソースローダを使用してリソース形式で読み取ります.
  • Resourceを使用したXMLファイルのインポート

    private class OxmSqlReader implements SqlReader {
            private Resource sqlmap = new ClassPathResource("sqlmap.xml", UserDao.class);
            
            public void setSqlmap(Resource sqlmap) {
                this.sqlmap = sqlmap;
            }
            public void read(SqlRegistry sqlRegistry) {
                try {
                    // 리소스 종류에 상관없이 스트림으로 가져올 수 있다.
                    Source source = new StreamSource(sqlmap.getInputStream());
                } catch ...
            }
    }
  • Resourceを使用する場合は、Resourceオブジェクトが実際のリソースではないことに注意してください.Resourceは、リソースにアクセスできる抽象的なハンドルにすぎません.したがって、リソースタイプのオブジェクトを作成しても、実際にはリソースが存在しない可能性があります.
  • 文字列として指定すると、リソースローダが認識できる文字列として表すことができます.たとえばclasspath:接頭辞を使用してクラスパスの音を表すことができます.
  • <bean id="sqlService" class="springbook.user.sqlservice.OxmSqlService">
        <property name="sqlMap" value="classpath:springbook/user/dao/sqlmap.xml" />
        ...
    </bean>
    特定の場所のファイル、Httpプロトコルにアクセス可能なWebリソースは、接頭辞を変更してインポートすることもできます.(file:, http:)

    インタフェースによる拡張セキュリティ機能の継承


    原則として推奨されませんが、サーバの実行中にSQLを変更する必要がある場合があります.アプリケーションを再起動するのではなく、アプリケーションが使用しているSQLを緊急に変更する必要がある場合があります.

    DIと機能拡張

  • DIの価値を得るには,まずDIに適したオブジェクトを設計する必要がある.
  • DIの目的は、実行時に依存オブジェクトを動的に接続して柔軟な拡張を実現することであるため、常に拡張を考慮し、オブジェクト間の関係を考慮しなければならない.
  • DIインタフェースとのプログラミング

  • DIをDIのようにするには、2つのオブジェクトがインタフェースを介してばらばらに接続されている必要があります.
  • の最初の理由は多形性を得るためである.1つ目の目的は、1つのインタフェースを介して複数のインプリメンテーションを交換し、使用可能にすることである.
  • の第2の理由は、インタフェース分離の原則が、クライアントと依存オブジェクトとの関係を明確にすることができるからである.
  • インタフェースの継承


    複数のインタフェースを作成するのではなく、継承された方法で既存のインタフェースを拡張する場合があります.
    public interface SqlRegistry {
        void registerSql(String key, String sql);
        Stirng findSql(String key) throws SqlNotFoundException;       
    }
    ここに登録されているSQLを追加することで拡張したいと考えています.
  • にはすでにSqlRegistryのクライアントがあるので、SqlRegistryを修正するのは理想的な方法ではありません.BaseSqlServiceオブジェクトはSqlRegistryインタフェースが提供する機能だけで十分です.
  • は、新しい機能を使用するクライアントの新しいインタフェースを定義するか、既存のインタフェースを拡張することを推奨します.
  • public interface UpdatableSqlRegistry extends SqlRegistry {
        void updateSql(String key, String sql) throws SqlUpdateFailureException;
        void updateSql(Map<String, String> sqlmap) throws SqlUpdateFailureException;
    }
  • がSQL更新機能を持つ新しいインタフェースを作成した以上、BaseSqlServiceも新しく作成したUpdatableSqlRegistryインタフェースを使用するべきではないでしょうか.そうではありません.
  • BassqSqlServiceはSQLレジストリを使用して初期化およびクエリーを行い、既存のSqlRegistryインタフェースからアクセスすれば十分です.
  • とは対照的に、SQL更新が必要な新しいクライアント・オブジェクトは、UpdatableSqlRegistryインタフェースを介してSqlレジストリにアクセスする必要があります.
  • SLQ管理オブジェクトが
  • SQLの変更を要求します.クラス名はSqlAdminServiceです.したがって、SqlAdminServiceは、UpdatableSqlRegistryというインタフェースを使用してSQLレジストリにアクセスする必要があります.

  • DI応用を用いた多様な実施方法


    実行中のシステムで使用される情報をリアルタイムで変更する場合は、まず同期性の問題を考慮します.

    ConcurrentHashMapを使用した変更可能なSQLレジストリ

  • マルチスレッド環境でHashMapを安全に操作するには、収集してください.SynchronizedMap()などで外部同期を行う.ただし、HashMapのすべてのタスクを同期させる場合、ハイパフォーマンス・サービスではパフォーマンスに問題が発生します.
  • 一般的にはConcurrentHashMapを使用することをお勧めします.ConcurrentHashMapでは、データの処理中にデータ全体が取捨選択されず、クエリでは取捨選択はまったく使用されません.
    したがって、一定のセキュリティとパフォーマンスを保証する同期HashMapとして使用するのに適しています.

    変更可能なSQLレジストリテスト

    public class ConcurrentHashMapSqlRegistryTest {
    	UpdatableSqlRegistry sqlRegistry;
    	
    	@Before
    	public void setUp() {
    		sqlRegistry = new ConcurrentHashMapSqlRegistry();
    		// 테스트 메소드에서 사용할 초기화 SQL 정보를 미리 등록한다.
    		sqlRegistry.registerSql("KEY1", "SQL1");
    		sqlRegistry.registerSql("KEY2", "SQL2");
    		sqlRegistry.registerSql("KEY3", "SQL3");
    	}
    
    	@Test
    	public void find() {
    		checkFindResult("SQL1", "SQL2", "SQL3");
    	}
    
    	private void checkFindResult(String expected1, String expected2, String expected3) {
    		assertThat(sqlRegistry.findSql("KEY1"), is(expected1));
    		assertThat(sqlRegistry.findSql("KEY2"), is(expected2));
    		assertThat(sqlRegistry.findSql("KEY3"), is(expected3));
    	}
    	
    	// 주어진 key에 해당하는 sql을 찾을 수 없을때 예외가 발생하는 지 확인하다. 예외상황에 대한 테스트는 빼먹기 쉽기에 항상 의식적으로 넣으려고 노력해야 한다.
    	@Test(expected=SqlNotFoundException.class)
    	public void unknownKey() {
    		sqlRegistry.findSql("SQL9999!@#$");
    	}
    	
    	// 하나의 sql 업데이트 테스트, 검증할 때는 나머지 sql은 그대로인지도 확인해주는 것이 좋다.
    	@Test
    	public void updateSingle() {
    		sqlRegistry.updateSql("KEY2", "Modified2");
    		checkFindResult("SQL1", "Modified2", "SQL3");
    	}
    	
    	// 여러개의 sql 수정 테스트
    	@Test
    	public void updateMulti() {
    		Map<String, String> sqlmap = new HashMap<String, String>();
    		sqlmap.put("KEY1", "Modified1");
    		sqlmap.put("KEY3", "Modified3");
    		
    		sqlRegistry.updateSql(sqlmap);
    		checkFindResult("Modified1", "SQL2", "Modified3");
    	}
    	
    	// 존재하지 않는 key의 sql을 변경하려고 시도할 때 예외가 발생하는 지 검증.
    	@Test(expected=SqlUpdateFailureException.class)
    	public void updateWithNotExistingKey() {
    		sqlRegistry.updateSql("SQL9999!@#$", "Modified2");
    	}
    
    }

    変更可能なSQLレジストリの実装

    public class ConcurrentHashMapSqlRegistry implements UpdatableSqlRegistry {
    	private Map<String, String> sqlMap = new ConcurrentHashMap<String, String>();
    
    	@Override
    	public String findSql(String key) {
    		String sql = sqlMap.get(key);
    		if (sql == null) {
    			throw new SqlNotFoundException(key + "에 해당하는 SQL을 찾을 수 없습니다");
    		} else {
    			return sql;
    		}
    	}
    
    	@Override
    	public void registerSql(String key, String sql) {
    		sqlMap.put(key, sql);
    	}
    
    	@Override
    	public void updateSql(String key, String sql) {
    		if(sqlMap.containsKey(key)) {
    			sqlMap.put(key, sql);
    		} else {
    			throw new SqlUpdateFailureException(key + "에 해당하는 SQL을 찾을 수 없습니다");
    		}
    	}
    
    	@Override
    	public void updateSql(Map<String, String> sqlmap) {
    		sqlMap.putAll(sqlmap);
    	}
    }

    埋め込みDBによる実装


    環境に格納されるデータ量が多くなり、クエリーや変更が頻繁に発生する場合はdbを使用します.
  • しかし、SQLの保存と管理を目的とする場合、個別のデータベースを構成することは、お腹よりも大きなことになる可能性があります.そのため、この場合は内蔵DBを使用することが望ましい.これにより、DBの利点と特徴が保持され、アプリケーション以外でデータベースをインストールしたり設定したりする必要がない.
  • 内蔵DBとは、アプリケーションに埋め込まれ、アプリケーションとともに起動および終了するDBのことである.メモリにデータが格納されているため、IOの負荷が非常に小さいため、パフォーマンスが非常に優れています.
  • Springの組み込みデータベースのサポート

  • スプリングは、埋め込みDBの初期化を支援する便利な内蔵DBビルダーを提供します.
  • new EmbeddedDatabaseBuilder() // 빌더 오브젝트 생성
        .setType(내장형DB종류) // H2, HSQL, DERBY 중에서 하나를 선택한다.
        .addScript(초기화 db script리소스) // 초기화를 위한 SQL문장을 담은 스크립트 위치를 지정한다. 하나 이상을 지정할 수 있다.
        ...
        .build(); // 주어진 조건에 맞는 내장형 DB를 준비하고 초기화 스크립트를 모두 실행한 뒤에 이에 접근할 수 있는 EmbeddedDatabase를 돌려준다.

    埋め込みデータベースを使用したSqlRegistryの作成

    <jdbc:embedded-database id="embeddedDatabase" type="HSQL">
        <jdbc:script location="classpath:schema.sql"/>
    </jdbc:embedded-database>
    public class EmbeddedDbSqlRegistry implements UpdatableSqlRegistry{
        SimpleJdbcTemplate jdbc;
        public void setDataSource(DataSource dataSource) {
            this.jdbc = new SimpleJdbcTemplate(dataSource); // 내장형 DB의 Datasource를 DI 받는다.
        }
        ...
        @Override
        public void updateSql(String key, String sql) throws SqlUpdateFailureException {
            // update()는 SQL 실행 결과로 영향을 받은 레코드의 개수를 리턴한다. 이를 이용하면 주어진 키(key)를 가진 SQL이 존재했는지를 간단히 확인할 수 있다.
            int affected = jdbc.update("update sqlmap set sql_ = ? where key_ = ?", sql, key);
            if(affected == 0) {
                throw new SqlUpdateFailureException(key + "에 해당하는 SQL을 찾을 수 없습니다.");
            }
        }
    }