1、mybatis的使用:
(1)、编写Mybatis的主配置文件
<?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>
<settings>
<setting name="useGeneratedKeys" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="logImpl" value="LOG4J"/>
</settings>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://***.***.***.***:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
(2)、新增Java实体与数据库记录建立的映射
实体类如下:
@Data
public class User {
private int id;
private String name;
private String password;
private String phone;
private String nick_name;
private Date create_time;
}
(3)、定义用于执行SQL的Mapper
接口类:
public interface UserMapper {
List<User> listAllUser();
User getUserById(@Param("id") int id);
}
映射配置文件:
<?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="**">
<sql id="userAllColumn">
id, name, password, phone, nick_name, create_time
</sql>
<resultMap id="user" type="**">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
<result property="nick_name" column="nick_name"/>
<result property="create_time" column="create_time"/>
</resultMap>
<select id="listAllUser" resultType="com.matrix.dao.User">
SELECT <include refid="userAllColumn"/> FROM user
</select>
<select id="getUserById" resultType="com.matrix.dao.User">
SELECT <include refid="userAllColumn"/> FROM user WHERE id = #{id}
</select>
</mapper>
(4)、通过Mybatis提供的API执行定义的Mapper
代码如下:
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.listAllUser();
System.out.println(users);
User user = userMapper.getUserById(1);
System.out.println(user);
2、Mybatis的核心组件
mybatis的执行流程及核心组件如下:
这些组件的作用如下:
(1)、Configuration:用于描述Mybatis的主配置信息,其他组件需要获取配置信息时,直接通过Configuration对象获取。除此之外,Mybatis在应用启动时,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息时,也可以从Configuration对象中获取。
(2)、MappedStatement:MappedStatement用于描述Mapper中的SQL配置信息,是对MapperXML配置文件中select|update|delete|insert等标签或者@Select/@Update等注解配置信息的封装。
(3)、SqlSession:SqlSession是Mybatis提供的面向用户的API,表示和数据库交互时的会话对象,用于完成数据库的增删改查的功能。SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的数据库操作接口。
(4)、Executor:Executor是Mybatis的SQL执行器,Mybatis中对数据库所有的增删改查操作都是由Executor组件完成的。
(5)、StatementHandler:StatementHandler封装了对JDBC Statement对象的操作,比如为Statement对象设置参数,调用Statement接口提供的方法和数据库交互等等。
(6)、ParameterHandler:当Mybatis框架使用的Statement类型为CallableStatement和PreparedStatement时,ParameterHandler用于为Statement对象参数占位符设置值。
(7)、ResultSetHandler:ResultSetHandler封装了对JDBC中的ResultSet对象操作,当执行SQL类型为SELECT语句时,ResultSetHandler用于将查询结果转换成Java对象。
(8)、TypeHandler:TypeHandler是Mybatis中的类型处理器,用于处理Java类型与JDBC类型之间的映射,它的作用主要体现在能够根据Java类型调用PreparedStatement或CallableStatement对象对应的setXxx()方法为Statement对象设置值,而且能够根据Java类型调用ResultSet对象对应的setXxx()方法获取SQL结果。
使用Mybatis操作数据库的过程。SqlSession组件,它是用户层面的API,实际上SqlSession是Executor组件的外观,目的是为用户提供更友好的数据库操作接口,这是设计模式中外观模式的典型使用。真正执行SQL操作的是Executor组件,Executor可以理解为SQL执行器,它会使用StatementHandler组件对JDBC的Statement对象进行操作。当Statement类型为CallableStatement和PreparedStatement时,会通过ParameterHandler组件为参数占位符赋值。ParameterHandler组件中会根据Java类型找到对应的TypeHandler对象,TypeHandler中会通过Statement对象提供的setXxx()方法为Statement对象中的参数占位符设置值。StatementHandler组件使用JDBC中的Statement对象与数据库完成交互后,当SQL语句类型为SELECT时,Mybatis通过ResultSetHandler组件从Statement对象中获取ResultSet对象,然后将ResultSet对象转换为Java对象。
2.1、Configuration详解
Mybatis框架的配置信息有两种,一种是配置Mybatis框架属性的主配置文件;另一种是配置执行SQL语句的Mapper配置文件。Configuration的作用是描述Mybatis主配置文件的信息。Configuration类中定义了一系列的属性用来控制Mybatis运行时的行为。这些属性代码如下:
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
其中的属性值可以在Mybatis主配置文件中通过setting标签指定。比如:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
Mybatis所有属性的含义
属性 | 作用 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 是否开启Mapper缓存,即二级缓存 | true/false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联的对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 | true/false | false |
aggressiveLazyLoading | 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 | true/false | false |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要JDBC驱动支持) | true/false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可以参考JDBC驱动相关文档或通过测试这两种不同的模式来观察JDBC驱动的结果 | true/false | true |
useGeneratedKeys | 允许JDBC支持自动生成主键,需要驱动兼容。如果设置为true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容,但仍可正常使用 | true/false | false |
autoMappingBehavior | 指定Mybatis应该如何自动映射列到Java实体属性。NONE表示取消自动映射,PARTIAL只会自动映射没有定义嵌套结果集映射的结果集。FULL会自动映射任意复杂的结果集(无论是否嵌套) | NONE/WARNING/FULL | NONE |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或者未知属性)的行为。NONE:不做任何反应。WARNING:输出提醒日志。FAILING:映射失败,抛出SqlSessionException异常 | NONE/WARNING/FAILING | NONE |
defaultExecutorType | 默认配置的Executor类型,SIMPLE就是普通的Executor;REUSE执行器会复用Statement对象;BATCH将会批量执行所有更新语句 | SIMPLE/REUSE/BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数 | 任意正整数 | 默认未设置,为null |
defaultFetchSize | 默认的FetchSize,用于设置Statement对象的的fetchSize属性,用于限制从数据库中获取数据的最大行数 | 任意正整数 | 默认未设置,为null,由具体的JDBC驱动决定 |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false | true/false | false |
safeResultHandlerEnabled | 允许在嵌套语句中使用分页(ResultHanldler)。如果允许使用,则设置为false | true/false | true |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则映射,即从经典数据库列名A_COLUMN到经典Java属性名aColumn的映射 | true/false | false |
localCacheScope | Mybatis利用本地缓存机制防止循环引用和加速重复查询。默认值为SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置为STATEMENT,本地会话仅用在语句执行上,对相同SqlSession的不同调用将不会共享数据 | SESSION/STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数指定JDBC类型时,指定JDBC类型的值为null。一些驱动需要指定JDBC类型,多数情况下直接用一般类型即可,比如NULL、VARCHAR或OTHER | jdbcType常量,通常为NULL、VARCHAR或OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法会触发一次延迟加载 | 用逗号分隔的方法列表 | equals、clone、hashCode、toString等 |
defaultScriptingLanguage | 指定动态SQL生成的默认语言 | 一个类型别名或完全限定类名 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定Java中枚举类型使用的默认TypeHandler | 一个类型别名或完全限定类名 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中的值为null的时候是否调用映射对象的Setter方法,这对于由Map.keySet()依赖或null值初始化的时候是非常有用。注意基本类型(int、boolean等)是不能设置成null的 | true/false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,Mybatis默认返回null。当开启这个设置时,Mybatis会返回一个空实例。请注意,也适用于嵌套的结果集(collection、association) | true/false | false |
logPrefix | 指定Mybatis增加到日志名称的前缀 | 任何字符串 | 未设置 |
logImpl | 指定Mybatis所用日志的具体实现,未指定时将自动查找 | SLF4J、LOG4J、LOG4J2、LOGGING等 | 未设置 |
proxyFactory | 指定Mybatis创建具有延迟加载能力的对象所用到的代理工具 | CGLIB、JAVASSIST | JAVASSIST |
vfsImpl | 指定VFS的实现 | VFS的实现类的全限定名,以逗号分隔 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。为了使用该特性,工程必须采用Java8编译,并且加上-parameters选项 | true/false | true |
configurationFactory | 指定一个提供Configuration实例的类。这个被返回的Configuration实例用来加载被反序列化对象的懒加载器属性值 | 这个类必须包含一个签名方法public static Configuration getConfiguration() | 未设置 |
Configuration除了提供了表中的属性控制Mybatis的行为外,还作为容器存放TypeHandler(类型处理器)、TypeAlias(类型别名)、Mapper接口及MapperSQL配置信息。这些信息在Mybatis启动时注册到Configuration组件中。
(1)、mapperRegistry:用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory对象之间的关系,其中MapperProxyFactory对象用于创建Mapper动态代理对象。
(2)、interruptChain:用于注册Mybatis插件信息,Mybatis插件实际上就是一个拦截器。
(3)、typeHandlerRegistry:用于注册所有的Typehandle,并建立Jdbc类型、JDBC类型与TypeHandler之间的对应关系。
(4)、typeAliasRegistry:用于注册所有的类型别名。
(5)、languageRegistry:用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象。
(6)、mappedStatements:MappedStatement对象描述inset/select/update/delete等标签或者通过@Select、@Delete、@Update、@Insert等注解配置的SQL信息,Mybatis将所有的MappedStatement对象注册到该属性中,其中Key为Mapper的ID,Value为MappedStatement对象。
(7)、caches:用于注册Mapper中配置的所有缓存信息,其中key为cache的ID,也就是Mapper的命名空间,value为cache对象。
(8)、resultMaps:用于注册Mapper配置文件中通过resultMap标签配置的ResultMap信息,ResultMap用于建立Java实体属性与数据库字段之间的映射关系,其中Key为ResultMap的ID,该ID是由Mapper命名空间和resultMap标签的ID属性组组成的,value为解析resultMap标签后得到的ResultMap对象。
(9)、parameterMaps:用于注册Mapper中通过parameterMap标签注册的参数映射信息。Key为parameterMap的ID,由Mapper命名空间和parameterMap标签的ID属性构成,value为解析parameterMap标签后得到的ParameterMap对象。
(10)、keyGenerators:用于注册KeyGenerator,KeyGenerator是Mybatis的主键生成器,Mybatis中提供了3种KeyGenerator,即Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、SelectKeyGenerator(通过select语句查询自增主键,例如oracle的sequence)。
(11)、loadedResources:用于注册所有Mapper XML配置文件路径。
(12)、sqlFragments:用于注册Mapper中通过sql标签配置的SQL片段,Key为SQL片段的ID,Value为Mybatis封装的表示XML节点的XNode对象。
(13)、incompleteStatement:用于注册解析出现异常的XMLStatementBuilder对象。
(14)、incompleteCacheRefs:用于注册解析出现异常的CacheRefResolver对象。
(15)、incompleteResultMaps:用于注册解析出现异常的ResultMapResolver对象。
(16)、incompleteMethods:用于注册解析出现异常的MethodResolver对象。
Mybatis框架启动时,会对所有的配置信息进行解析,然后将解析后的内容注册到Configuration对象的这些属性中。
除此之外,Configuration组件还作为Executor、StatementHandler、ResultSetHandler、ParameterHandler组件的工厂类,用于创建这些组件的实例。Configuration类中提供了这些组件的工厂方法,这些方法如下:
(1)、ParameterHandlers组件工厂方法
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
(2)、ResultSetHandler组件工厂方法
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
(3)、StatementHandler组件工厂方法
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
(4)、Executor组件工厂方法
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这些工厂方法会根据Mybatis不同的配置创建对应的实现类。Mybatis采用工厂模式创建Executor、StatementHandler、ResultSetHandler、ParameterHandler的另一个目的是实现插件拦截器。
2.2、Executor详解
SqlSession是Mybatis提供的操作数据库的API,但是真正执行SQL的是Executor组件,Executor接口中定义了对数据库的增删改查方法,其中query()和queryCursor()方法用于执行查询操作。update()方法用于执行插入、删除、修改操作。Executor接口有几种不同的实现类。
Mybatis提供了三种不同的Executor,分别为SimpleExecutor、ReuseExecutor、BatchExecutor这些Executor都继承自BaseExecutor,BaseExecutor中定义的方法的执行流程及通用的处理逻辑,具体的方法由子类来实现,是典型的模版方法模式的应用。SimpleExecutor是基础的Executor,能够完成基本的增删改查操作,ReuseExecutor对JDBC中的Statement对象做了缓存,当执行相同的SQL语句时,直接从缓存中取出Statement对象进行复用,避免了频繁创建和销毁Statement对象,从而提升系统性能。这是享元思想的应用。BatchExecutor则会对调用同一个Mapper执行的update、insert和delete操作,调用Statement对象的批量操作功能。另外Mybatis支持一级缓存和二级缓存,当Mybatis开启了二级缓存功能时,会使用CachingExecutor对SimpleExecutor、ReuseExecutor、BatchExecutor进行装饰,为查询操作增加二级缓存功能,这是装饰器模式的应用。
2.3、MappedStatement详解
Mybatis通过MappedStatement描述select|update|insert|delete或者@Select、@Update等注解配置的SQL信息。
select标签中的属性:
<select id="getUserById"
parameterType="int"
parameterMap="deprecated"
resultType="com.matrix.dao.User"
resultMap="user"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
SELECT <include refid="userAllColumn"/> FROM user WHERE id = #{id}
</select>
这些属性的含义如下:
(1)、id:在命名空间中唯一的标识符,可以被用来引用这条配置信息。
(2)、parameterType:用于指定这条语句的参数类的完全限定名或别名。这个属性是可选的,Mybatis能够根据Mapper接口方法中的参数类型推断出传入语句的类型。
(3)、parameterMap:引用通过parameterMap标签定义的参数映射,该属性已经废弃。
(4)、resultType:从这条语句中返回的期望类型的类的完全限定名或别名。注意,如果返回结果是集合类型,则resultType属性应该指定集合中可以包含的类型,而不是集合本身。
(5)、resultMap:用于引用通过resultMap标签配置的实体属性与数据库字段之间建立的结果集的映射(注意,resultMap和resultType属性不能同时使用)。
(6)、flushCache:用于控制是否刷新缓存。如果将其设置为true,则任何时候只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值为false。
(7)、useCache:是否使用二级缓存。如果将其设置为true,则会导致本条语句的结果被缓存在Mybatis的二级缓存中,对应select标签,该属性的默认值为true。
(8)、timeout:驱动程序等待数据库返回请求结果的秒数,超时将会抛出异常。
(9)、fetchSize:用于设置JDBC中Statement对象的fetchSize属性,该属性用于指定SQL执行后返回的最大行数。
(10)、statementType:参数可选值为STATEMENT、PREPARED或CALLABLE,这会让Mybatis分别使用Statement、PreparedStatement或CallableStatement与数据库交互。默认值为PREPARED。
(11)、resultSetType:参数可选值为FORWARD_ONLY、SCROLL_SENSITIVE或SCROLL_INSENSITIVE,用于设置ResultSet对象的特征。
(12)、databaseId:如果配置了databaseIdProvider,Mybatis会加载所有不带databaseId或匹配当前databaseId的语句。
(13)、resultOrdered:这个设置仅针对嵌套结果select语句使用,如果为true,就是假定嵌套结果包含在一起或分组在一起,这样的话,当返回一个主结果行的时候,就不会发生对前面结果集引用的情况。这就使得在获取嵌套结果集的时候不至于导致内存不够用,默认值为false。
(14)、resultSets:这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称使用逗号分隔。
(15)、lang:该属性用于指定LanguageDriver实现,Mybatis中的LanguageDriver用于解析select|update|insert|delete标签中的SQL语句,生成SqlSource对象。
上面的属性中,resultMap、resultType、resultSetType、fetchSize、useCache、resultSets、resultOrdered是select标签特有的,其它属性是update|insert|delete标签共有的。另外insert和update标签有几个特有的属性。
insert标签中的属性:
(1)、useGeneratedKeys:这属性仅对update和insert标签有用,属性值为true时,Mybatis使用JDBC Statement对象的getGeneratedKeys()方法来取出由数据库内部生成的键值。例如Mysql自增主键。
(2)、keyProperty:该属性仅对update和insert标签有用,用于将数据库自增主键或者insert标签中的selectKey标签返回的值填充到实体的属性中,如果有多个属性,则使用逗号分隔。
(3)、keyColumn:该属性仅对update和insert标签有用,通过生成的键值设置表中的列名,这个设置仅在某些数据库中是必须的,当主键不是表中第一列时需要设置,如果有多个字段,则使用分号分隔。
MappedStatement类通过下面这些属性保存select|update|insert|delete标签的属性信息:
// Mapper资源路径
private String resource;
// Configuration对象的引用
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
// 解析SQL语句生成的SqlSource实例
private SqlSource sqlSource;
// 二级缓存实例
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
// 主键生成策略
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
// 是否有嵌套的ResultMap
private boolean hasNestedResultMaps;
private String databaseId;
// 输出日志
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
2.4、StatementHandler详解
StatementHandler组件封装了对JDBC Statement的操作,例如设置Statement对象的fetchSize属性,设置查询超时时间、调用JDBC Statement与数据库交互等。
Mybatis的StatementHandler接口中定义的方法如下:
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
这些方法的作用如下:
(1)、prepare:该方法用于创建JDBC Statement对象,并生成Statement对象的属性设置。
(2)、parameterize:该方法使用Mybatis中的ParameterHandler组件为PreparedStatement和CallableStatement参数占位符设置值。
(3)、batch:将SQL命令添加到批处理执行表中。
(4)、update:调用Statement对象的execute()方法执行更新语句,例如UPDATE、INSERT、DELETE语句。
(5)、query:执行查询语句,并使用ResultSetHandler处理查询结果集。
(6)、queryCursor:带游标的查询,返回Cursor对象,能够通过Iterator动态地从数据库中加载数据,适用于查询数据量较大的情况,避免将所有数据加载到内存中。
(7)、getBoundSql:获取Mapper中配置的SQL信息,BoundSql封装了动态SQL解析后的SQL文本和参数映射信息。
(8)、getParameterHandler:获取ParameterHandler实例。
StatementHandler接口有几种不同的实现:
BaseStatementHandler是一个抽象类,封装了通用的处理逻辑及方法执行流程,具体方法的实现由子类来完成这里使用到了设计模式中的模版方法模式。
SimpleStatementHandler继承自BaseStatementHandler,封装了对JDBC Statement对象的操作,PreparedStatementHandler封装了对JDBC PreparedStatement对象的操作,而CallableStatementHandler则封装了对JDBC CallableStatement对象的操作。RoutingStatementHandler会根据Mapper配置中的statementType属性(取值为STATEMENT、PREPARED或CALLABLE)创建对应的StatementHandler实现。
2.5、TypeHandler详解
使用JDBC API开发应用程序,其中一个比较繁琐的环节是处理JDBC类型与Java类型之间的转换。涉及Java类型和JDBC类型转换的两种情况如下:
(1)、PreparedStatement对象为参数占位符设置值时,需要调用PreparedStatement接口中提供的一系列setXxx()方法,将Java类型转换为对应的JDBC类型并为参数占位符赋值。
(2)、执行SQL语句获取ResultSet对象后,需要调用ResultSet对象的getXxx()方法获取字段值,此时会将JDBC类型转换为Java类型。
Mybatis中使用TypeHandler解决上面两种情况下,JDBC类型与Java类型之间的转换。
TypeHandler接口定义如下:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
TypeHandler接口中定义了4个方法,setParameter()方法用于为PreparedStatement对象参数的占位符设置值,另外3个重载的getResult()方法用于从ResultSet对象中获取列的值,或者获取存储过程调用结果。
Mybatis中的BaseTypeHandler类实现了TypeHandler接口,对调用setParameter()方法,参数为Null值的情况做了通用处理。对调用getResult()方法,从ResultSet对象或存储过程调用结果中获取列的值出现的异常做了处理。因此,当我们需要自定义TypeHandler时,只需要继承BaseTypeHandler类即可。
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean,boolean | 数据库兼容的BOOLEAN |
ByteTypeHandler | java.lang.Byte,byte | 数据库兼容的NUMBER或BYTE |
ShortTypeHandler | java.lang.Short,short | 数据库兼容的NUMBER或SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer,int | 数据库兼容的NUMBER或INTEGER |
LongTypeHandler | java.lang.Long,long | 数据库兼容的NUMBERIC或INTEGER LONG |
FloatTypeHandler | java.lang.Float,float | 数据库兼容的NUMBER或FLOAT |
DoubleTypeHandler | java.lang.Double,double | 数据库兼容的NUMBER或DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的NUMBER或DECIMAL |
StringTypeHandler | java.lang.String | CHAR、VARCHAR |
ClobReaderTypeHandler | java.io.Reader | - |
ClobTypeHandler | java.lang.String | CLOB,LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR,NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | - |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BOLB、LONGVARBINARY |
DateTypeHandler | java.util.Date | TIMESTAMP |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
ObjectTypeHandler | Any | OTHER或未指定类型 |
EnumTypeHandler | 枚举 | VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler | 枚举 | 任何兼容的NUMBER或DOUBLE类型,存储枚举的索引(而不是名称) |
InstantTypeHandler | java.time.Instant | TIMESTAMP |
LocalDateTimeTypeHandler | java.time.LocalDateTime | TIMESTAMP |
LocalDateTypeHandler | java.time.LocalDate | DATE |
LocalTimeTypeHandler | java.time.LocalTime | TIME |
OffsetTimeTypeHandler | java.time.OffsetTime | TIME |
OffsetDateTimeTypeHandler | java.time.OffsetDateTime | TIMESTAMP |
ZonedDateTimeTypeHandler | java.time.ZonedDateTime | TIMESTAMP |
YearTypeHandler | java.time.Year | INTEGER |
MonthTypeHandler | java.time.Month | INTEGER |
YearMonthTypeHandler | java.time.YearMonth | VARCHAR、LONGVARCHAR |
JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | DATE |
Mybatis通过TypeHandlerRegistry建立JDBC类型,Java类型与TypeHandler之间的映射关系。代码如下:
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
2.6、ParameterHandler详解
当使用PreparedStatement或者CallableStatement对象时,如果SQL语句中有参数占位符,在执行SQL语句之前,就需要为参数占位符设置值。ParameterHandler的作用是在PreparedStatementHandler和CallableStatementHandler操作对应的Statement执行数据库交互之前为参数占位符设置值。ParameterHandler接口中只有两个方法,使用方法如下:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
这两个方法的作用如下:
(1)、getParameterObject:该方法用于获取执行Mapper时传入的参数对象。
(2)、setParameters:该方法用于为JDBC PreparedStatement或者CallableStatement对象设置参数值。
ParameterHandler接口只有一个默认的实现类,即DefaultParameterHandler。
setParameters方法如下:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
首先获取Mapper配置中的参数映射,然后对所有参数映射信息进行遍历,接着根据参数名称获取对应的参数值,调用对应的TypeHandler对象的setParameter()方法为Statement对象中的参数占位符赋值。
2.7、ResultSetHandler详解
ResultSetHandler用于在StatementHandler对象执行完查询操作或存储过程后,对结果集或存储过程的执行结果进行处理。ResultSetHandler接口的定义如下:
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
如上所示,ResultSetHandler接口中有3个方法,这些方法的作用如下:
(1)、handleResultSets:获取Statement对象中的ResultSet对象,对ResultSet对象进行处理。
(2)、handleCursorResultSets:将ResultSet对象包装成Cursor对象,对Cursor进行遍历时,能够动态地从数据库中查询数据,避免一次性将所有数据加载到内存中。
(3)、handleOutputParameters:处理存储过程调用结果。
ResultSetHandler接口只有一个默认实现,即DefaultResultHandler。
handleResultSets()方法的实现:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
(1)、从Statement对象中获取ResultSet对象,然后将ResultSet包装为ResultSetWrapper对象,通过ResultSetWrapper对象能够更方便地获取便字段名称,字段对应的TypeHandler信息。
(2)、获取解析Mapper接口及Mapper SQL配置生成的ResultMap信息,一条语句一般对应一个ResultMap。
(3)、调用handleResultSet()方法对ResultSetWrapper对象进行处理,将生成的实体对象存放在multipleResults列表中。
具体后续会详细分析。