前提小知识
- 数据库操作的常规步骤
1.加载数据库驱动
2.根据认证信息获取数据库连接
3.开启事务
4.创建statement
5.执行sql
6.处理结果集
7.提交事务
8.关闭资源
- mybatis官方学习文档地址
http://www.mybatis.org/mybatis-3/
从源码看mybatis configuration 中几个主要的配置都是什么作用
/**
* 解析mybatis配置文件,从根节点configuration开始解析
* @param root
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
/*读取配置的属性信息 */
propertiesElement(root.evalNode("properties"));
/*解析setting节点*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
/*实体类型别名注册*/
typeAliasesElement(root.evalNode("typeAliases"));
/*拦截器注册*/
pluginElement(root.evalNode("plugins"));
/*对象工厂*/
objectFactoryElement(root.evalNode("objectFactory"));
/*对象包装工厂*/
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
/*自定义反射器工厂类*/
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
/*多数据库厂商 数据库ID的生成实现类*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/*注册Java类型 与 数据库字段类型的对应关系 处理器*/
typeHandlerElement(root.evalNode("typeHandlers"));
/*注册数据库操作的接口*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
typeAliases 实体别名配置 这个理解和使用都比较简单【注:大小写不敏感 别名全部会转成小写】
1.注册类的别名
/*****详见TypeAliasRegistry.java start*******/
public void registerAlias(Class<?> type) {
/*默认是类的简单名称*/
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
/*如果有Alias注解且值不为空,则使用注解配置的别名注册*/
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
/*转成小写*/
String key = alias.toLowerCase(Locale.ENGLISH);
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
TYPE_ALIASES.put(key, value);
}
/*****详见TypeAliasRegistry.java end*******/
2.别名的使用
/*****详见BaseBuilder.java start*******/
protected Class<?> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
/*****详见BaseBuilder.java end*******/
/*****详见TypeAliasRegistry.java start*******/
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (TYPE_ALIASES.containsKey(key)) {
/*如果存在别名就直接按照别名获取class*/
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
/*不存在别名配置则按照全路径获取class*/
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
/*****详见TypeAliasRegistry.java end*******/
/*****别名注册简单示例 start*******/
package typeAlias;
import org.apache.ibatis.type.Alias;
@Alias("testAlias")
public class TestAlias {
private String test;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
/*****别名注册简单示例 end*******/
<!--在mapper 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="test.CachedAuthorMapper">
<!--别名方式 -->
<parameterMap id="s" type="testAlias" >
<parameter property="test" javaType="string" jdbcType="VARCHAR"/>
</parameterMap>
<resultMap id="BASE_MAP" type="testAlias">
<result jdbcType="VARCHAR" javaType="string" property="test" column="username"/>
</resultMap>
<select id="testAlias" resultType="testAlias" parameterType="testAlias">
</select>
<!--全路径方式 -->
<select id="selectAuthorWithInlineParams"
parameterType="int"
resultType="org.apache.ibatis.domain.blog.Author">
select * from author where id = #{id}
</select>
</mapper>
plugins 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>
...
<typeAliases>
<package name="typeAlias"/>
</typeAliases>
<plugins>
<!-- 可以直接使用别名 -->
<plugin interceptor="testPlugin">
<property name="testProd" value="hello mybatis plugin"/>
</plugin>
</plugins>
...
</configuration>
拦截器自定义实现简单示例
@Alias("testPlugin")
/*声明要拦截的类和方法【明确指定方法参数个数和类型】*/
@Intercepts({
@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class TestPlugin implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(TestPlugin.class);
private String testProd;
@Override
public Object intercept(Invocation invocation) throws Throwable {
logger.info(testProd);
return invocation.getMethod().invoke(invocation.getTarget(),invocation.getArgs());
}
@Override
public Object plugin(Object target) {
/*使用mybatis为我们提供好的默认处理方式*/
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
this.testProd = properties.getProperty("testProd");
}
}
注册拦截器的解析入口
/************详见XMLConfigBuilder.java******************************* */
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
//在InterceptorChain中注册
configuration.addInterceptor(interceptorInstance);
}
}
}
拦截器的实际注册类
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
/*为拦截对象返回代理对象*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
*注册拦截器
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
2.拦截器的使用
mybatis默认会在以下四个对象上使用plugin
- ParameterHandler
- ResultSetHandler
- StatementHandler
- Executor
/***************详见Configuration.java start**************************/
/**
*为ParameterHandler对象生成代理对象
*/
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
/***************详见Configuration.java end**************************/
/***************InterceptorChain.java start**************************/
/**
* 调用拦截器plugin方法生成代理对象 默认实现为Plugin.wrap 详见下文
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/***************InterceptorChain.java end**************************/
拦截器代理对象InvocationHandler实现,真正处理切面逻辑的地方
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
/**
* 返回对象的代理对象
* @param target
* @param interceptor
* @return
*/
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*如果方法是拦截器要拦截的方法,则调用拦截器的拦截方法*/
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
/*不在拦截列表则不做任何处理*/
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
/**
* 获取拦截器拦截的方法列表
* @param interceptor
* @return
*/
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
objectFactory 返回结果对象生成工厂 下面是默认实现
package org.apache.ibatis.reflection.factory;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.ibatis.reflection.ReflectionException;
/**
* @author Clinton Begin
*/
public class DefaultObjectFactory implements ObjectFactory, Serializable {
private static final long serialVersionUID = -8855120656740914948L;
@Override
public <T> T create(Class<T> type) {
return create(type, null, null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Class<?> classToCreate = resolveInterface(type);
// we know types are assignable
return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
@Override
public void setProperties(Properties properties) {
// no props for default
}
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {
constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
}
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (Exception e) {
StringBuilder argTypes = new StringBuilder();
if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
for (Class<?> argType : constructorArgTypes) {
argTypes.append(argType.getSimpleName());
argTypes.append(",");
}
argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
}
StringBuilder argValues = new StringBuilder();
if (constructorArgs != null && !constructorArgs.isEmpty()) {
for (Object argValue : constructorArgs) {
argValues.append(String.valueOf(argValue));
argValues.append(",");
}
argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
}
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}
protected Class<?> resolveInterface(Class<?> type) {
Class<?> classToCreate;
if (type == List.class || type == Collection.class || type == Iterable.class) {
classToCreate = ArrayList.class;
} else if (type == Map.class) {
classToCreate = HashMap.class;
} else if (type == SortedSet.class) { // issue #510 Collections Support
classToCreate = TreeSet.class;
} else if (type == Set.class) {
classToCreate = HashSet.class;
} else {
classToCreate = type;
}
return classToCreate;
}
@Override
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}
}
environments 环境 顾名思义 可以配置多个隔离的环境 -> 开发/ 测试/ 预发/ 生产
<?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="dev">
<!-- 开发-->
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxx.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--测试 -->
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxx.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--生产 -->
<environment id="prod">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxx.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
...
</configuration>
简单使用示例
public static void main(String[] args) {
InputStream inputStream;
try {
inputStream = Resources.getResourceAsStream("mybatis.xml");
//开发
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"dev");
// 测试
//SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"test");
//生产
// SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"prod");
SqlSession sqlSession = sqlSessionFactory.openSession();
CachedAuthorMapper cachedAuthorMapper = sqlSession.getMapper(CachedAuthorMapper.class);
Author author = cachedAuthorMapper.selectAllAuthors(1);
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
databaseIdProvider 生成数据库厂商标识
<?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://xxx.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--DB_VENDOR 为 VendorDatabaseIdProvider 别名 -->
<databaseIdProvider type="DB_VENDOR">
<!-- mysql数据库标识-->
<property name="MySQL" value="mysql"/>
<!-- Oracle数据库标识-->
<property name="oracle" value="oracle"/>
</databaseIdProvider>
<mappers>
<mapper resource="test/CachedAuthorMapper.xml"/>
</mappers>
</configuration>
<?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="ddshuai.CachedAuthorMapper">
<!--当数据库为MySQL 执行这一条-->
<select id="searchNow" databaseId="mysql" resultType="date">
select now() from dual
</select>
<!--当数据库为Oracle 执行这一条-->
<select id="searchNow" databaseId="oracle" resultType="date">
select sysdate() from dual
</select>
</mapper>
typeHandler java类型 与 数据库类型 映射处理器
简单实现一个typeHandler 下面的typeHandler负责加密数据库的自增主键 并实现可逆转换
@Alias("idHandler")
public final class IdTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setLong(i, IDEncodeUtil.decode(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
final long l = rs.getLong(columnName);
return IDEncodeUtil.encode(l);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
final long l = rs.getLong(columnIndex);
return IDEncodeUtil.encode(l);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
final long l = cs.getLong(columnIndex);
return IDEncodeUtil.encode(l);
}
}
public abstract class IDEncodeUtil {
public static String encode(long l) {
if (l < 0) {
return Long.toString(l);
} else{
l = mix(l);
return Long.toString(l, 36);
}
}
public static long decode(String s) {
if(s.startsWith("-")) {
return Long.parseLong(s);
} else {
return demix(Long.parseLong(s, 36));
}
}
private static long mix(long l) {
final long[] vs = doMix(l);
return setVersion(vs);
}
private static long[] doMix(long l) {
final long version = 1L;
long ret = l;
int digit = 0;
while (ret > 0) {
digit++;
ret = ret >> 3;
}
int i = 0, md = (digit - 1) / 5 + 1;
final int mix = (int) (l & ((1 << (3 * md)) - 1));
ret = 0;
while (digit > 0) {
ret += (((l & ((1 << 15) - 1)) + ((mix & (((1 << 3) - 1) << (3 * --md))) << (15 - 3 * md))) << i);
l = (l >> 15);
digit -= 5;
i += 18;
}
l = ret;
return new long[] { version, l };
}
private static long demix(long l) {
final long[] vs = getVersion(l);
l = vs[1];
switch ((int) vs[0]) {
case 1:
long dig = 0,
ret = 0;
while (l > 0) {
ret += ((l & ((1 << 15) - 1)) << dig);
l = (l >> 18);
dig += 15;
}
l = ret;
break;
}
return l;
}
private static long setVersion(long[] vs) {
// return vs[1] / 256 * 4096 + vs[0] * 256 + vs[1] % 256;
return ((vs[1] >> 8) << 12) + (vs[0] << 8) + (vs[1] & 255);
}
private static long[] getVersion(long l) {
// return new long[] { (l / 256) % 16, (l / 4096) * 256 + l % 256 };
return new long[] { (l >> 8) & 15, ((l >> 12) << 8) + (l & 255) };
}
}
mybatis注册上面的typeHandler
/******************详见Configuration.java****************************/
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
<?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>
...
<typeAliases>
<package name="typeAlias"/>
</typeAliases>
<typeHandlers>
<typeHandler handler="idHandler" javaType="string" jdbcType="long"/>
</typeHandlers>
...
</configuration>
结果
@Override
public String toString() {
return "Author : " + id + " : " + username + " : " + email;
}
数据库记录
id username email bio
1 ddshuai ddshuai@139.com sdssd
DEBUG [main] - ==> Preparing: select * from author where id = ?;
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
Author : b8qp : ddshuai : ddshuai@139.com