>  기사  >  Java  >  Java의 Spring 최대 절전 모드에서 테이블 이름을 동적으로 바꾸는 방법

Java의 Spring 최대 절전 모드에서 테이블 이름을 동적으로 바꾸는 방법

黄舟
黄舟원래의
2017-08-09 10:05:142335검색

1. 개요

사실 가장 간단한 방법은 session.createSQLQuery("sql")와 같은 네이티브 SQL을 사용하거나 jdbcTemplate을 사용하는 것입니다. 하지만 프로젝트에 hql 쿼리가 사용되었기 때문에 수정이 힘들고 위험합니다! 따라서 더 나은 해결책을 찾아야 합니다. 작동하지 않으면 다시 작성하세요! 3일간의 연구 끝에 마침내 좋은 방법을 찾았습니다. 이에 대해서는 아래에 설명되어 있습니다.

2. 단계

2.1 새로운 최대 절전 모드 인터셉터 클래스를 생성합니다.

/**
 * Created by hdwang on 2017/8/7.
 *
 * hibernate拦截器:表名替换
 */
public class AutoTableNameInterceptor extends EmptyInterceptor {

    private String srcName = StringUtils.EMPTY; //源表名
    private String destName = StringUtils.EMPTY; // 目标表名

    public AutoTableNameInterceptor() {}

    public AutoTableNameInterceptor(String srcName,String destName){
        this.srcName = srcName;
        this.destName = destName;
    }


    @Override
    public String onPrepareStatement(String sql) {
        if(srcName.equals(StringUtils.EMPTY) || destName.equals(StringUtils.EMPTY)){
            return  sql;
        }
        sql = sql.replaceAll(srcName, destName);
        return sql;
    }
}

이 인터셉터는 모든 데이터베이스 작업을 가로채서 전송하기 전에 SQL 문의 테이블 이름을 바꿉니다.

2.2 sessionFactory로 구성

먼저 sessionFactory가 무엇인지 살펴보겠습니다.


<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" >
        <property name="dataSource" ref="defaultDataSource"></property>
        <property name="packagesToScan">
            <list>
                <value>com.my.pay.task.entity</value>
                <value>com.my.pay.paycms.entity</value>
                <value>com.my.pay.data.entity.payincome</value>
            </list>
        </property>
        <property name="mappingLocations">
             <list>
                 <value>classpath*:/hibernate/hibernate-sql.xml</value>
             </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">false</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <!-- 开启查询缓存 -->
                <prop key="hibernate.cache.use_query_cache">false</prop>
                <!-- 配置二级缓存 --> 
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->  
                  <prop key="hibernate.cache.use_structured_entries">true</prop> 
                  <!-- Hibernate将收集有助于性能调节的统计数据 -->  
                  <prop key="hibernate.generate_statistics">false</prop>
                  <!-- 指定缓存配置文件位置 -->
                  <prop key="hibernate.cache.provider_configuration_file_resource_path">/spring/ehcache.xml</prop>
                <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
                <prop key="hibernate.current_session_context_class">jta</prop>
                <prop key="hibernate.transaction.factory_class">org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory</prop>
                <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop>
            </props>
        </property>
    </bean>
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
        implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {

    private DataSource dataSource;

    private Resource[] configLocations;

    private String[] mappingResources;

    private Resource[] mappingLocations;

    private Resource[] cacheableMappingLocations;

    private Resource[] mappingJarLocations;

    private Resource[] mappingDirectoryLocations;

    private Interceptor entityInterceptor;

    private NamingStrategy namingStrategy;

    private Object jtaTransactionManager;

    private Object multiTenantConnectionProvider;

    private Object currentTenantIdentifierResolver;

    private RegionFactory cacheRegionFactory;

    private Properties hibernateProperties;

    private Class<?>[] annotatedClasses;

    private String[] annotatedPackages;

    private String[] packagesToScan;

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private Configuration configuration;

    private SessionFactory sessionFactory;

사실 sessionFactory는 LocalSessionFactoryBean 객체의 속성입니다. 이는 왜 Bean 주입이 클래스 자체가 아닌 클래스의 속성인지 알 수 있습니다. 이는 FactoryBeana7bd7c0154d420bde695d24770c97980 인터페이스를 구현합니다. sessionFacotry는 LocalSessionFactoryBean 객체를 구성하여 생성됩니다. 생성 후 sessionFactory 객체는 스프링 컨테이너에 주입되며 이는 기본적으로 싱글톤입니다.
우리 모두는 sessionFactory 객체에 의해 생성된 세션 객체를 사용하여 데이터베이스를 운영합니다. 다음은 sessionFactory 개체의 두 가지 메서드입니다.

    /**
     * Open a {@link Session}.
     * <p/>
     * JDBC {@link Connection connection(s} will be obtained from the
     * configured {@link org.hibernate.service.jdbc.connections.spi.ConnectionProvider} as needed
     * to perform requested work.
     *
     * @return The created session.
     *
     * @throws HibernateException Indicates a problem opening the session; pretty rare here.     */
    public Session openSession() throws HibernateException;    /**
     * Obtains the current session.  The definition of what exactly "current"
     * means controlled by the {@link org.hibernate.context.spi.CurrentSessionContext} impl configured
     * for use.
     * <p/>
     * Note that for backwards compatibility, if a {@link org.hibernate.context.spi.CurrentSessionContext}
     * is not configured but JTA is configured this will default to the {@link org.hibernate.context.internal.JTASessionContext}
     * impl.
     *
     * @return The current session.
     *
     * @throws HibernateException Indicates an issue locating a suitable current session.     */
    public Session getCurrentSession() throws HibernateException;

그런 다음 프로젝트에서는 getCurrentSession()을 사용하여 세션 개체를 얻습니다.

최대 절전 모드 인터셉터를 구성하는 방법은 무엇입니까?

LocalSessionFactoryBean对象的entityInterceptor属性可以配置,你可以在xml中配置它,加到sessionFactory这个bean的xml配置中去。


<property name="entityInterceptor">
      <bean class="com.my.pay.common.AutoTableNameInterceptor"/>
      </property>

글쎄, 하나만 구성할 수 있습니다. sessionFactory는 싱글톤이므로, sessionFactory를 참조하는 Dao 객체도 싱글톤이고, 서비스와 컨트롤러도 모두 싱글톤입니다. 그렇다면 질문이 있습니다. 테이블 이름을 동적으로 바꾸는 방법은 무엇입니까? 동적 다중 사례로 가는 길이 막혔습니다. 남은 것은 인터셉터 객체의 값을 동적으로 수정하는 것뿐입니다. 좋은 조언인 것 같습니다. 나의 시도는 실패로 끝났고 스레드 안전 문제를 해결할 수 없었습니다! 그 이유는 나중에 설명하겠습니다.

그래서 XML로 구성하면 내 요구 사항을 충족할 수 없습니다. 그런 다음 코드에서만 설정할 수 있습니다. 다행스럽게도 sessionFactory 객체는 이를 수정할 수 있는 입구를 제공합니다.


@Resource(name = "sessionFactory")private SessionFactory sessionFactory;protected Session getSession(){        
if(autoTableNameInterceptorThreadLocal.get() == null){            
return this.sessionFactory.getCurrentSession();
        }else{
            SessionBuilder builder = this.sessionFactory.withOptions().interceptor(autoTableNameInterceptorThreadLocal.get());
            Session session = builder.openSession();            
            return session;
        }
}
/**
* 线程域变量,高效实现线程安全(一个请求对应一个thread)
*/
private ThreadLocal<AutoTableNameInterceptor> autoTableNameInterceptorThreadLocal = new ThreadLocal<>();

public List<WfPayLog> find(Long merchantId, Long poolId,String sdk, Long appId,String province,
            Integer price,
            String serverOrder, String imsi,Integer iscallback,String state,
            Date start, Date end, Paging paging) {
       。。。。

        //定制表名拦截器,设置到线程域
        autoTableNameInterceptorThreadLocal.set(new AutoTableNameInterceptor("wf_pay_log","wf_pay_log_"+ DateUtil.formatDate(start,DateUtil.YEARMONTH_PATTERN)));
        List<WfPayLog> wfPayLogs;
        if (paging == null) {
            wfPayLogs = (List<WfPayLog>) find(hql.toString(), params); //find方法里面有 this.getSession().createQuery("hql") 等方法
    } else { 
       wfPayLogs = (List<WfPayLog>) findPaging(hql.toString(), "select count(*) " + hql.toString(), params, paging); 
    }
       return wfPayLogs; 
}

빨간색으로 표시된 코드가 핵심코드이자 핵심설명입니다. 즉, DAO 레이어 객체에 sessionFactory 객체를 주입하여 세션을 획득하는 방식으로 데이터베이스를 운영할 수 있도록 변경했습니다. 테이블 이름을 변경해야 할 때, 스레드 도메인 변수를 정의하고, 인터셉터가 필요할 때 인터셉터 객체를 스레드 도메인에 저장합니다. 그러면 데이터베이스를 운영할 수 있도록 인터셉터로 구성된 세션을 얻게 됩니다. time 인터셉터가 적용됩니다.

객체 멤버 변수를 스레드 도메인 변수에 저장하지 않고 직접 정의하는 것은 확실히 불가능합니다. 동시성 문제가 있기 때문입니다(여러 요청(스레드)이 동시에 dao 메서드를 호출하고 getSession() 메서드가 호출됨) dao 메소드가 실행될 때, 아마도 gettingSession 시 인터셉터가 다른 요청에 대해 교체되었을 때) 물론 동기화를 통해 해결할 수도 있습니다. 스레드 도메인을 사용하는 것은 동기화된 동기화 잠금보다 훨씬 효율적입니다. 스레드 도메인을 사용하면 인터셉터 개체와 요청(스레드)이 함께 연결됩니다. dao 메서드가 실행될 때 실행 문이 동일한 스레드에 있는 한 스레드가 공유하는 개체 정보는 일관성이 있어야 합니다. 이므로 동시성 문제가 없습니다.

위에서 언급했듯이 싱글톤 인터셉터는 스레드 안전성 문제를 해결할 수 없기 때문에 작동하지 않습니다. AutoTableNameInterceptor는 싱글톤입니다. 새 집합 작업을 추가하는 등 dao 계층에서 해당 값을 수정할 수 있습니다. 그러나 설정하는 동안 다른 요청도 설정되므로 요청이 직렬(대기열에 하나씩 대기)이 아닌 이상 destName 및 srcName의 값이 계속 변경됩니다. 그리고 n dao 인스턴스가 인터셉터를 호출할 수도 있습니다. 스레드 동기화를 어떻게 달성합니까? dao 작업 중에 전체 인터셉터 개체를 잠그지 않으면 성능에 영향을 미칩니다! 이것은 스레드 도메인을 사용하여 달성할 수 없습니다. 테스트 후 우리는 요청 스레드 대신 인터셉터 메소드를 호출하는 최대 절전 모드 하단에 여러 스레드가 있음을 발견했습니다. 따라서 dao에서 인터셉터까지는 더 이상 스레드가 아닙니다. 인터셉터의 onPrepareStatement 콜백 메소드는 너무 단조롭고 기능이 제한되어 있습니다. 게다가 싱글톤을 사용하는 것은 sessionFactory의 전역 구성이므로 코드를 통해 추가하는 것은 일시적입니다.

위 내용은 Java의 Spring 최대 절전 모드에서 테이블 이름을 동적으로 바꾸는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.