Mybatis 1 급 캐 시 와 Spring Framework 를 결합 한 후 실 효 된 소스 코드 탐구
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/hrh/mapper/PersonMapper.xml"/>
</mappers>
</configuration>
PersonMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hrh.mapper.PersonMapper">
<resultMap id="BaseResultMap" type="com.hrh.bean.Person">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="age" property="age" jdbcType="BIGINT"/>
</resultMap>
<sql id="Base_Column_List">
id, name, age
</sql>
<select id="list" resultType="com.hrh.bean.Person">
select
<include refid="Base_Column_List"/>
from tab_person
</select>
</mapper>
public interface PersonMapper {
List<Person> list();
}
String resource = "mybatis-config2.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();//
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
mapper.list();
mapper.list();
이러한 상황 이 발생 한 이 유 는 Mybatis 에 1 급 캐 시 가 존재 하기 때 문 입 니 다.아래 debug 는 내부 절 차 를 탐구 합 니 다.
(1)mapper.list()는 MapperProxy\#invoke()에 들 어 갑 니 다.매개 변수 proxy 는 하나의 프 록 시 대상(모든 Mapper 인터페이스 가 하나의 프 록 시 대상 으로 변 환 됩 니 다)이 고 세 션 sqlSession,인터페이스 정보,방법 정 보 를 포함 합 니 다.method 는 목표 방법(현재 실행 하 는 방법)입 니 다.그 안에 속 하 는 어떤 종류(인터페이스),방법 명,반환 유형(List,Map,void 또는 기타),매개 변수 유형 등 이 포함 되 어 있 습 니 다.args 는 매개 변수 입 니 다.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// methodCache : 、 (select、update )、
// MapperMethod, methodCache
final MapperMethod mapperMethod = cachedMapperMethod(method);
// SQL
return mapperMethod.execute(sqlSession, args);
}
cacheMapperMethod:MapperMethod 는 방법 명,유형(select,update 등),반환 유형 등 정 보 를 포함 합 니 다.
private MapperMethod cachedMapperMethod(Method method) {
//
MapperMethod mapperMethod = methodCache.get(method);
//
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
(2)MapperMethod\#execute()는 SQL 형식 에 따라 서로 다른 조회 방법 에 들어간다.
public Object execute(SqlSession sqlSession, Object[] args) {
//
Object result;
//
switch (command.getType()) {
case INSERT: {//
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {//
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {//
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT://
//
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// List
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
//
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
(3)위의 사례 는 selection 문 구 였 고 결 과 는 List 집합 이 었 기 때문에 MapperMethod\#execute Formany()에 들 어 갔다.
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//
Object param = method.convertArgsToSqlCommandParam(args);
//
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
// list ,
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
(4)selectList 는 DefaultSqlSession\#selectList()를 실 행 했 습 니 다.
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//SQL :resource(xxMapper.xml)、id、sql、
MappedStatement ms = configuration.getMappedStatement(statement);
//
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
(5)다음 캐 시 실행 기 를 호출 하 는 방법:CachingExecutor\#query()
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// SQL , ,
//createCacheKey: BaseExecutor#createCacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
(6)다음 에 BaseExecutor\#query()를 실행 합 니 다.결 과 를 localCache 에 캐 시 하 는 것 을 아래 에서 볼 수 있 습 니 다.
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// ( 0), <select> flushCache=true
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// +1
queryStack++;
// localCache
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
//
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//
localCache.removeObject(key);
}
//
localCache.putObject(key, list);
//
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
2.그러나 Spring Framework+Mybatis 는 상황 이 다 릅 니 다.조회 할 때마다 데이터 베 이 스 를 연결 하여 조회 하고 콘 솔 에서 SQL 을 인쇄 합 니 다.다음 과 같은 사례 가 있 습 니 다.
@Service
public class PersonService {
@Autowired
PersonMapper personMapper;
public List<Person> getList() {
personMapper.list();
personMapper.list();
return personMapper.list();
}
}
@Configuration
@ComponentScan("com.hrh")
@MapperScan("com.hrh.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setMapperLocations(resolveMapperLocations());
return factoryBean;
}
public Resource[] resolveMapperLocations() {
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<String> mapperLocations = new ArrayList<>();
mapperLocations.add("classpath*:com/hrh/mapper/*Mapper*.xml");
List<Resource> resources = new ArrayList();
if (mapperLocations != null) {
for (String mapperLocation : mapperLocations) {
try {
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
} catch (IOException e) {
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
driverManagerDataSource.setUsername("xxx");
driverManagerDataSource.setPassword("xxx");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
return driverManagerDataSource;
}
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
PersonService bean = context.getBean(PersonService.class);
bean.getList();
아래 debug 가 들 어 가 는 절 차 는 위의(1),(2),(3)와 일치 하지만 네 번 째 단 계 는 SqlSession Template\#selectList()에 들 어 가 는 것 입 니 다.
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
다음 selectList()는 방법 에 의 해 차 단 됩 니 다.method.invoke()는 DefaultSqlSession\#selectList()로 실 행 됩 니 다.다시 위의 네 번 째 단계 로 돌아 가 계속 합 니 다.즉,위의(1)~(6)에 앞 뒤 글 을 삽입 하여 회 의 를 닫 는 작업 을 합 니 다.
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);//
}
return result;
} catch (Throwable t) {//
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
//
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
요약:Mybatis 의 1 급 캐 시 는 세 션 등급 의 캐 시(단일 스 레 드,특히 계 륵)입 니 다.Mybatis 는 SqlSession 세 션 대상 을 만 들 때마다 데이터베이스 세 션 을 열 었 다 는 뜻 입 니 다.한 세 션 에서 응용 프로그램 은 짧 은 시간 안에 같은 검색 어 를 반복 적 으로 실행 할 수 있 습 니 다.데이터 에 캐 시 하지 않 으 면매번 조회 할 때마다 데이터베이스 조 회 를 실행 해 야 하기 때문에 데이터베이스 자원 의 낭 비 를 초래한다.또한 SqlSession 을 통 해 실 행 된 작업 은 실제로 Executor 에서 데이터베이스 작업 을 수행 하기 때문에 Executor 에서 간단 한 캐 시,즉 1 급 캐 시 를 만 듭 니 다.매번 조회 결 과 를 캐 시 하고 조 회 를 다시 실행 할 때 1 급 캐 시(기본적으로 열 린 것)를 먼저 조회 합 니 다.명중 하면 바로 돌아 갑 니 다.그렇지 않 으 면 데이터 베 이 스 를 조회 하고 캐 시 에 넣 습 니 다.
1 급 캐 시 수명 주 기 는 SqlSession 의 수명 주기 와 같 습 니 다.따라서 Mybatis 와 Spring Framework 의 통합 패키지 에 SqlSession Template 클래스(프 록 시 클래스 로 검색 방법 을 강화 하 였 습 니 다)가 확장 되 었 을 때 모든 조 회 는 SqlSession Template 프 록 시 를 거 쳐 DefaultSqlSession\#selectList()에 들 어 갑 니 다.검색 을 마 친 후 세 션 SqlSession 을 닫 아서 캐 시가 효력 을 잃 었 습 니 다.
근 데 왜 이렇게 해 야 돼 요?
원시 적 인 Mybatis 는 SqlSession 인 터 페 이 스 를 노출 시 켰 기 때문에 close 방법 이 노출 되 어 사용 할 수 있 습 니 다.닫 는 것 과 닫 지 않 는 것 을 선택 할 수 있 습 니 다.그러나 Mybatis 와 Spring Framework 의 통합 패키지 에서 SqlSession 은 Spring Framework 관리 에 맡 겼 고 노출 되 지 않 았 으 며 안정 적 인 결정 을 위해 직접 닫 았 습 니 다.
Mybatis 1 급 캐 시 와 Spring Framework 를 결합 한 후 실 효 된 소스 코드 에 대한 탐 구 를 소개 합 니 다.Mybatis 1 급 캐 시 Spring Framework 의 실효 내용 에 대해 서 는 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부탁드립니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
SpringMVC와 Mybatis 집합은 호출 저장 프로세스, 사무 제어 실례를 실현한다SSM 프레임워크에서 호출 데이터베이스의 저장 프로세스와 사무 제어에 자주 사용되는데 다음은 증빙서류를 저장하는 예를 들어 소개한다. 1. Oracle에 저장된 프로세스 코드는 다음과 같습니다(주요 논리는 증빙 서류...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.