우리 프로젝트에서 다음과 같은 문제에 직면했습니다. 우리 프로젝트는 여러 데이터베이스에 연결해야 하며, 다양한 고객이 방문할 때마다 필요에 따라 서로 다른 데이터베이스에 액세스하게 됩니다. 과거에는 항상 Spring 및 Hibernate 프레임워크에서 데이터 소스를 구성했으므로 sessionFactory의 dataSource 속성은 항상 이 데이터 소스를 가리키며 sessionFactory를 사용할 때 모든 DAO는 이 데이터 소스를 통해 데이터베이스에 액세스합니다. 그러나 이제 프로젝트의 요구로 인해 DAO는 sessionFactory에 액세스할 때 여러 데이터 소스 간에 지속적으로 전환해야 합니다. 데이터 지속성을 수행할 때 sessionFactory를 고객의 요구에 따라 동적으로 전환하도록 만드는 방법이 다릅니다. 데이터 소스? 스프링 프레임워크에서 몇 가지 수정만으로 문제를 해결할 수 있나요? 활용할 수 있는 디자인 패턴이 있나요?
문제 분석
처음에는 Spring의 applicationContext에서 모든 데이터 소스를 구성하는 것을 생각했습니다. 이러한 데이터 소스는 Oracle, SQL Server, MySQL 등과 같은 다양한 데이터베이스와 같은 다양한 유형일 수 있습니다. 또는 Apache에서 제공하는 org.apache.commons.dbcp.BasicDataSource, spring에서 제공하는 org와 같은 다양한 데이터 소스일 수 있습니다. .springframework.jndi.JndiObjectFactoryBean 등. 그런 다음 sessionFactory는 데이터 소스 전환 목적을 달성하기 위해 각 고객 요청에 따라 dataSource 속성을 다른 데이터 소스로 설정합니다.
그런데, 여러 사용자가 동시에 데이터베이스에 접속하면 리소스 경합이 발생한다는 문제를 금방 발견했습니다. 이는 모두 "단일 사례 패턴"으로 인해 발생합니다. 우리 모두 알고 있듯이 스프링 프레임워크를 사용할 때 beanFactory에 등록된 Bean은 기본적으로 싱글톤 모드를 채택합니다. 즉, Spring이 시작되면 이러한 Bean이 메모리에 로드되고 각 Bean은 전체 프로젝트에만 존재합니다. 물체. 객체가 하나뿐이므로 객체의 모든 속성, 더 정확하게는 인스턴스 변수가 정적 변수처럼 동작합니다(실제로 "정적"과 "단일 사례"는 매우 유사한 경우가 많습니다. "싱글톤"을 구현하기 위해 "정적"을 사용하는 경우가 많습니다) ). 문제를 예로 들면, sessionFactory에는 전체 프로젝트에서 단 하나의 개체만 있고 해당 인스턴스 변수 dataSource에는 정적 변수와 마찬가지로 단 하나의 인스턴스 변수만 있습니다. 여러 사용자가 지속적으로 dataSource의 값을 수정하는 경우 필연적으로 여러 사용자가 변수를 두고 경쟁하는 문제가 발생하게 되며 이로 인해 시스템에 숨겨진 위험이 발생하게 됩니다.
위의 분석을 통해 다중 데이터 소스 액세스 문제를 해결하는 핵심은 특정 코드 조각을 통해 고객 요구에 따라 동적으로 전환하는 sessionFactory의 기능에 중점을 둡니다. 데이터 지속성을 수행하고 리소스 경합 문제를 해결합니다.
문제 해결
Decorator 디자인 패턴 사용
이 문제를 해결하기 위해 내 생각은 이 데이터 소스에 고정되었습니다. sessionFactory가 가리키는 dataSource가 고객의 요구에 따라 고객이 필요로 하는 실제 데이터소스에 연결할 수 있다면, 즉 데이터소스를 동적으로 전환하는 기능을 제공한다면 문제는 해결될 것이다. 그럼 우리는 무엇을 합니까? 사용하려는 dataSource 소스 코드를 수정해야 합니까? 이것은 확실히 좋은 해결책은 아닙니다. 우리는 수정 사항을 원본 dataSource 코드와 분리하기를 원합니다. 위의 분석을 바탕으로 GoF 디자인 패턴에서 데코레이터 패턴(Decorator Pattern)을 사용하는 것이 우리가 선택할 수 있는 최선의 선택이 될 것입니다.
'데코레이터 모드'란 무엇인가요? 간단히 말하면, 원래 함수를 수정해야 하지만 원래 코드를 직접 수정하고 싶지 않은 경우 원래 코드를 덮도록 Decorator를 설계합니다. Decorator를 사용하면 원래 클래스와 완전히 동일하지만, Decorator의 일부 기능을 수정해야 할 기능으로 수정했습니다. Decorator 패턴의 구조는 그림과 같습니다.
원래 다이어그램에 있는 모든 특정 구성 요소 클래스의 일부 기능을 수정해야 했지만 코드를 직접 수정하는 대신 외부에 데코레이터를 추가했습니다. Decorator와 특정 Component 클래스는 모두 AbstractComponent를 상속하므로 특정 Component 클래스와 동일하게 보입니다. 즉, Decorator를 사용하면 ConcreteComponentA 또는 ConcreteComponentB를 사용하는 것과 같으며 심지어 ConcreteComponentA 또는 ConcreteComponentB를 사용하는 클라이언트 프로그램도 마찬가지입니다. 사용하는 클래스가 Decorator로 변경된 것을 모르지만 Decorator가 특정 Component 클래스의 일부 메소드를 수정하여 해당 메소드를 실행한 결과가 다릅니다.
MultiDataSource 클래스 디자인
이제 문제로 돌아가서 dataSource의 기능을 변경해야 하지만 dataSource의 코드는 수정하고 싶지 않습니다. 여기서 말하는 dataSource는 javax.sql.DataSource 인터페이스를 구현한 모든 클래스이며, 일반적으로 사용되는 클래스로는 apache에서 제공하는 org.apache.commons.dbcp.BasicDataSource, spring에서 제공하는 org.springframework.jndi.JndiObjectFactoryBean 등이 있다. 우리는 이러한 클래스를 직접 수정하는 것은 불가능하지만, 데이터 소스를 동적으로 할당하는 기능을 구현하기 위해 하나씩 수정하는 것은 물론, dataSource를 사용하는 sessionFactory가 이러한 변화를 느끼지 않기를 바랍니다. 모두. Decorator 패턴은 이러한 문제를 해결하는 디자인 패턴입니다.
먼저 MultiDataSource라는 이름의 Decorator 클래스를 작성하고 이를 사용하여 데이터 소스를 동적으로 전환합니다. 동시에 구성 파일에서 sessionFactory의 dataSource 속성을 특정 dataSource에서 MultiDataSource로 변경합니다. 그림과 같이:
원래 Decorator 모드와 비교할 때 AbstractComponent는 추상 클래스이지만 여기서는 이 추상 클래스를 인터페이스, 즉 DataSource 인터페이스 및 ConcreteComponent는 BasicDataSource, JndiObjectFactoryBean 등과 같은 DataSource 구현 클래스입니다. MultiDataSource는 특정 데이터 소스를 캡슐화하고 데이터 소스의 동적 전환을 구현합니다.
Java 코드
public class MultiDataSource implements DataSource { private DataSource dataSource = null; public MultiDataSource(DataSource dataSource){ this.dataSource = dataSource; } /* (non-Javadoc) * @see javax.sql.DataSource#getConnection() */ public Connection getConnection() throws SQLException { return getDataSource().getConnection(); } //其它DataSource接口应当实现的方法 public DataSource getDataSource(){ return this.dataSource; } } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
고객이 요청하면 dataSourceName이 요청에 배치되고 데이터가 요청에 배치됩니다. is 소스 이름은 newMultiDataSource(dataSource)를 호출하여 MultiDataSource에 고객이 필요로 하는 데이터 소스를 알려줄 수 있으므로 데이터 소스를 동적으로 전환할 수 있습니다. 그러나 조심성 있는 친구들은 이것이 싱글톤의 경우 문제라는 것을 알게 될 것입니다. 왜냐하면 MultiDataSource는 시스템에 단 하나의 객체만을 갖고 있고 그 인스턴스 변수 dataSource 역시 정적 변수처럼 단 하나의 객체만을 갖고 있기 때문입니다. 이로 인해 싱글톤 패턴으로 인해 많은 디자인 패턴이 변경되었습니다. 이에 대해서는 내 기사 "싱글톤이 디자인 패턴을 변경합니다"에서 자세히 논의할 것입니다. 그렇다면 싱글톤 모드에서는 어떻게 디자인할까요?
싱글톤 모드의 MultiDataSource
싱글톤 모드에서는 MultiDataSource 메서드를 호출할 때마다 dataSource가 달라질 수 있으므로 dataSource를 인스턴스 변수 dataSource에 넣을 수 없습니다. 가장 간단한 방법은 getDataSource() 메소드에 매개변수를 추가하여 내가 호출하는 데이터 소스를 MultiDataSource에 알려주는 것입니다.
java code
public DataSource getDataSource(String dataSourceName){ log.debug("dataSourceName:"+dataSourceName); try{ if(dataSourceName==null||dataSourceName.equals("")){ return this.dataSource; } return (DataSource)this.applicationContext.getBean(dataSourceName); }catch(NoSuchBeanDefinitionException ex){ throw new DaoException("There is not the dataSource } }
스프링 구성 파일에 등록되어 있으며 dataSourceName은 해당 ID입니다.
xml 코드
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>oracle.jdbc.driver.OracleDrivervalue> property> ...... bean> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>oracle.jdbc.driver.OracleDrivervalue> property> ...... bean>
Spring의 ApplicationContext를 얻으려면 MultiDataSource 클래스가 org.springframework.context.ApplicationContextAware 인터페이스와 구현 방법을 구현해야 합니다.
java 코드
private ApplicationContext applicationContext = null; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
이런 식으로 this.applicationContext.getBean(dataSourceName)을 통해 dataSource를 얻을 수 있습니다.
스레드를 통해 dataSourceName 전달
위 디자인을 보면 MultiDataSource는 사용자가 요청할 때 어떤 데이터베이스에 연결해야 합니까? 데이터 소스 이름은 요청에서 MultiDataSource에 데이터 소스 이름을 전달하려면 BUS 및 DAO를 거쳐야 합니다. 즉, 데이터 소스 이름을 MultiDataSource에 전달하려면 BUS 및 DAO의 모든 메소드를 거쳐야 합니다. DAO는 dataSourceName 매개변수를 추가해야 합니다. 이것은 우리가 보고 싶지 않은 것입니다. BUS 및 DAO를 건너뛰고 스레드를 통해 MultiDataSource에 직접 전달하는 클래스를 작성하는 것이 좋습니다.
java code
public class SpObserver { private static ThreadLocal local = new ThreadLocal(); public static void putSp(String sp) { local.set(sp); } public static String getSp() { return (String)local.get(); } }
필터 만들기 , 클라이언트가 요청할 때마다 SpObserver.petSp(dataSourceName)가 호출되고 요청의 dataSourceName이 SpObserver 개체에 전달됩니다. 마지막으로 MultiDataSource 메서드 getDataSource()를 수정합니다.
java code
public DataSource getDataSource(){ String sp = SpObserver.getSp(); return getDataSource(sp); }
전체 MultiDataSource 코드는 첨부 파일에 있습니다.
동적으로 데이터 소스 추가
通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在spring的ApplicationContext 中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇到。spring的 ApplicationContext是在项目启动的时候加载的。加载以后,我们如何动态地加载新的bean到ApplicationContext中 呢?我想到如果用spring自己的方法解决这个问题就好了。所幸的是,在查看spring的源代码后,我找到了这样的代码,编写了 DynamicLoadBean类,只要调用loadBean()方法,就可以将某个或某几个配置文件中的bean加载到 ApplicationContext中(见附件)。不通过配置文件直接加载对象,在spring的源码中也有,感兴趣的朋友可以自己研究。
在spring中配置
在完成了所有这些设计以后,我最后再唠叨一句。我们应当在spring中做如下配置:
xml 代码
<bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean> <bean id="dataSource" class="com.htxx.service.dao.MultiDataSource"> <property name="dataSource"> <ref bean="dataSource1" /> property> bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> property> ...... bean>
其中dataSource属性实际上更准确地说应当是defaultDataSource,即spring启动时以及在客户没有指定数据源时应当指定的默认数据源。
该方案的优势以上方案与其它方案相比,它有哪些优势呢?
首先,这个方案完全是在spring的框架下解决的,数据源依然配置在spring的配置文件中,sessionFactory依然去配置它的 dataSource属性,它甚至都不知道dataSource的改变。唯一不同的是在真正的dataSource与sessionFactory之间增 加了一个MultiDataSource。
其次,实现简单,易于维护。这个方案虽然我说了这么多东西,其实都是分析,真正需要我们写的代码就只有MultiDataSource、 SpObserver两个类。MultiDataSource类真正要写的只有getDataSource()和getDataSource(sp)两个 方法,而SpObserver类更简单了。实现越简单,出错的可能就越小,维护性就越高。
最后,这个方案可以使单数据源与多数据源兼容。这个方案完全不影响BUS和DAO的编写。如果我们的项目在开始之初是单数据源的情况下开发,随着项 目的进行,需要变更为多数据源,则只需要修改spring配置,并少量修改MVC层以便在请求中写入需要的数据源名,变更就完成了。如果我们的项目希望改 回单数据源,则只需要简单修改配置文件。这样,为我们的项目将增加更多的弹性。
特别说明:实例中的DynamicLoadBean在web环境下运行会出错,需要将类中 AbstractApplicationContext改为 org.springframework.context.ConfigurableApplicationContext。
更多Spring 프레임워크에서 여러 데이터 소스를 생성, 로드 및 동적으로 전환하기 위한 구성 예제 코드相关文章请关注PHP中文网!