1.前言
这一章节从题目中也看出来我们要支持注解版的增删改查,可以在Mapper层的接口类的方法上写Sql语句,如:Insert,Update,Delete,Select的这几个基础Sql,如下图,这样就不用在Xml里去写Sql了,但是这就引申出一个问题,我们怎么解析注解的Sql语句,将方法上的SQL内容取出来并按之前的既定流程去处理执行Sql呢?
我们现在要处理以下几步才能达到目的
1.配置文件中去配置注解类,加上此在解析的时候才能知道什么情况下用XML方式,在什么情况下用注解方式
2.在配置解析时如果是注解类,直接将class值为namespce进行代理注册
3.开始解析注解,找到Mapper类里所有的方法,每一个方法都判断下是不是加了这4种注解
3.1.获取Sql内容,传输到以前就有的流程里得到处理过后的Sql语句,SqlSource
3.2.通过反射获取方法的参数、返回值、参数类型、返回值类型、并将获取到的数据映射到MappedStatement以及ResultMap
4.之后的流程不变
2.Uml类图
看到类图我们知道,这一章节其实也是很简单的,涉及添加的类只有几个,涉及的修改也不多
添加了4个注解类,1个解析注解类MappernnotationBuilder
XmlConfiguration这里修改判断下如果是注解方式直接调用MapperRegistry的addMapper方法将当前的class进行代理注册,
MapperRegistry类的addMapper方法里添加调用MappernnotationBuilder解析注解数据。
MappernnotationBuilder拿到Sql原内容后调用XmlLanguageDriver进行Sql源处理,继续解析参数、返回值、调用MapperBuilderAsisstant的addMappedStatement和addResultMaps方法将所得的数据映射到MappedStatement和ResultMap类中,供后续使用,之后的就还是和之前的流程一样。
3.代码实现
XMLConfigBuilder类:此类解析Mapper时添加从xml中获取Class,如果这个Class不为空,证明是要解析注解类,通过反射将String的class类转换为类对象,并通过configuration添加到mapper映射里。
// 处理mapper的方法,mapper里有多个sql语句,所以需要List
private void mapperElement(Element mappers) throws Exception {
// 得到mybatis-config-datasource.xml的mappers标签里的mapper
List<Element> mapperList = mappers.elements("mapper");
for (Element e : mapperList) {
String resource = e.attributeValue("resource");
String mapperClass = e.attributeValue("class");
// xml解析方式
if (resource != null && mapperClass == null) {
InputStream inputStream = Resources.getResourceAsStream(resource);
// 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);
mapperParser.parse();
} else if (resource == null && mapperClass != null) {
// 注解方式
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
}
}
}
MapperRegistry类:此类里就添加了解析注解类的处理调用。
public <T> void addMapper(Class<T> type) {
// 是接口
if (type.isInterface()) {
if (hasMapper(type)) {
// 如果重复添加了报错
throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
}
// 注册映射器代理工厂
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析注解类语句配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
}
}
MapperAnnotationBuilder类:此类本节新添加的类,专门处理注解的解析,如sql,参数,返回值等操作,
1.parse方法,拿到class的所有方法,如IuserDao定义的所有方法。
2.循环遍历每一个方法并调用parseStatement(method)方法,
2.1 获取参数类型,调用getParameterType(method);
2.2 获取语言驱动器,目的是为了创造Sql源,调用getLanguageDriver(method) 2.3 获取数据源,通过调用getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver)方法。
2.3.1 通过调用getSqlAnnotationType(method)方法,获取注解类型 2.3.2 根据得到的注解类型获取当前方法上的注解信息,并得到方法上的注解值。 2.3.3 将内容Sql、参数类型、语言驱动器传递,调用buildSqlSourceFromStrings(); 2.3.3.1 将原Sql、参数类型传递给Sql源进行处理,调用createSql();
3.生成mappedStatementId标识
4.根据注解类型处理获取Sql标签,如INSERT、DELETE,,,,,调用getSqlCommandType(method);
5.判断标签是否是查询,是去解析结果,调用parseResultMap(method);
6.所有的数据已备全,将数据添加到mappedStatement,调用助手类的addMappedStatement()方法。
public class MapperAnnotationBuilder {
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();
private MapperBuilderAssistant assistant;
private Configuration configuration;
private Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace(".", "/") + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
}
/**
解析
*/
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
assistant.setCurrentNamespace(type.getName());
// 获取类里所有的方法
Method[] methods = type.getMethods();
for (Method method : methods) {
if (!method.isBridge()) {
// 解析语句
parseStatement(method);
}
}
}
}
/**
* 解析语句
*/
private void parseStatement(Method method) {
// 通过方法获取参数类型
Class<?> parameterTypeClass = getParameterType(method);
// 获取默认驱动器XmlLanguageDriver
LanguageDriver languageDriver = getLanguageDriver(method);
// 根据注解上的Sql内容获取数据源
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
// mappedStatementId标识
final String mappedStatementId = type.getCanonicalName() + "." + method.getName();
// 获取SQL标签,INSERT、UPDATE...
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
String resultMapId = null;
if (isSelect) {
// 是查询的化解析返回值
resultMapId = parseResultMap(method);
}
// 注册到mappedStatement里
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
sqlCommandType,
parameterTypeClass,
resultMapId,
getReturnType(method),
languageDriver
);
}
}
/**
* 重点:DAO 方法的返回类型,如果为 List 则需要获取集合中的对象类型
*/
private Class<?> getReturnType(Method method) {
Class<?> returnType = method.getReturnType();
if (Collection.class.isAssignableFrom(returnType)) {
Type returnTypeParameter = method.getGenericReturnType();
if (returnTypeParameter instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length == 1) {
returnTypeParameter = actualTypeArguments[0];
if (returnTypeParameter instanceof Class) {
returnType = (Class<?>) returnTypeParameter;
} else if (returnTypeParameter instanceof ParameterizedType) {
returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
} else if (returnTypeParameter instanceof GenericArrayType) {
Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
returnType = Array.newInstance(componentType, 0).getClass();
}
}
}
}
return returnType;
}
/** 解析返回类型处理为ResultMap */
private String parseResultMap(Method method) {
StringBuffer suffix = new StringBuffer();
// 拼接参数类型
for (Class<?> c : method.getParameterTypes()) {
suffix.append("-");
suffix.append(c.getSimpleName());
}
// 拼接空返回值
if (suffix.length() < 1) {
suffix.append("-void");
}
//df.middleware.mybatis.dao.IUserDao.queryInfoId-long-void
String resultMapId = type.getName() + "." + method.getName() + suffix;
// 添加ResultMap
Class<?> returnType = getReturnType(method);
assistant.addResultMap(resultMapId,returnType,new ArrayList<>());
return resultMapId;
}
/** 根据注解类型获取Sql标签 */
private SqlCommandType getSqlCommandType(Method method) {
Class<? extends Annotation> type = getSqlAnnotationType(method);
if (type == null) {
return SqlCommandType.UNKNOWN;
}
return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
}
/**
* 根据注解的值(Sql)获取Sql源
* */
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
// 获取注解类型
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
if (sqlAnnotationType != null) {
// 根据注解类型获取注解类
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
// 获取注解类的值-原始Sql语句
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
// 根据获取的注解类的值构建Sql源
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
}
return null;
} catch (Exception e) {
throw new RuntimeException("Could not find value method on SQL annotation. Cause: " + e);
}
}
/**
* 拼接SQL,创造数据源
* */
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);
}
/**
* 获取注解类型
* */
private Class<? extends Annotation> getSqlAnnotationType(Method method) {
for (Class<? extends Annotation> type : sqlAnnotationTypes) {
Annotation annotation = method.getAnnotation(type);
if (annotation != null) return type;
}
return null;
}
/**
* 获取语言驱动器
* */
private LanguageDriver getLanguageDriver(Method method) {
Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
return configuration.getLanguageRegistry().getDriver(langClass);
}
/**
* 获取参数类型
* */
private Class<?> getParameterType(Method method) {
Class<?> parameterType = null;
// 根据方法参数获取参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> clazz : parameterTypes) {
if (!RowBounds.class.isAssignableFrom(clazz) && !ResultHandler.class.isAssignableFrom(clazz)) {
if (parameterType == null) {
parameterType = clazz;
} else {
parameterType = MapperMethod.ParamMap.class;
}
}
}
return parameterType;
}
}
MapperBuilderAssistant类:
此类添加addResultMap()方法,将ResultMap存储到Configuration中的ResultMaps变量里,并修改了setStatementResultMap()方法里补充了之前的逻辑空缺,如果resultMap不为空,则将Configuration的ResultMaps变量数据取出存储到MappedStatement里。
public class MapperBuilderAssistant{
//id=此形式 df.middleware.mybatis.dao.IUserDao.queryInfoId-long-void
public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
configuration,
id,
type,
resultMappings);
ResultMap resultMap = inlineResultMapBuilder.build();
// 为啥在configuration里添加,不直接用上头的方法
configuration.addResultMap(resultMap);
return resultMap;
}
// 封装ResultMap对象
private void setStatementResultMap(
String resultMap,
Class<?> resultType,
MappedStatement.Builder statementBuilder
) {
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
}
} else if (resultType != null) {
// 省略...
}
statementBuilder.resultMaps(resultMaps);
}
}
LanguageDriver类:语言驱动器重载了createSqlSource方法,参数里script是String形式的原Sql(注解解析出来的),而原来的则是脚本文件,还需xml脚本构建器处理,进一步解析
public interface LanguageDriver {
// 省略其他....
SqlSource createSqlSource(Configuration configuration,String script,Class<?> parameterType);
}
XmlLanguageDriver类:
实现新添加的 createSqlSource方法,由于已经是Sql数据了,所以此处直接new RawSqlSource,这时候后续流程就都一样了,RawSqlSource只需要处理后续流程将原Sql语句的参数处理成?即可。
/**
* 用于处理注解配置
* */
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
return new RawSqlSource(configuration, script, parameterType);
}
Delete注解类
包:package df.middleware.mybatis.annotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {
String[] value();
}
INSERT注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Insert {
String[] value();
}
Select、Update注解类都是一样的,这里就不展示了,
Configuration里的修改
// 结果映射,存在Map里-新增
protected final Map<String, ResultMap> resultMaps = new HashMap<>();
public ResultMap getResultMap(String id) {
return resultMaps.get(id);
}
public void addResultMap(ResultMap resultMap) {
resultMaps.put(resultMap.getId(), resultMap);
}
4.测试准备
mybatis-config-datasource.xml : 数据库环境及注解或Xml mapper配置,此处修改成注解方式
<mapper class="df.middleware.mybatis.dao.IUserDao"></mapper>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_demo?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper resource="mapper/User_Mapper.xml"/>-->
<!-- 注解类配置 -->
<mapper class="df.middleware.mybatis.dao.IUserDao"></mapper>
</mappers>
</configuration>
Dao层使用注解
public interface IUserDao {
@Select("SELECT id, userId, userName, userHead\n" +
"FROM user\n" +
"where id = #{id}")
User queryUserInfoById(Long id);
@Select("SELECT id, userId, userName, userHead\n" +
" FROM user\n" +
" where id = #{id}")
User queryUserInfo(User req);
@Select("SELECT id, userId, userName, userHead\n" +
"FROM user")
List<User> queryUserInfoList();
@Update("UPDATE user\n" +
"SET userName = #{userName}\n" +
"WHERE id = #{id}")
int updateUserInfo(User req);
@Insert("INSERT INTO user\n" +
"(userId, userName, userHead, createTime, updateTime)\n" +
"VALUES (#{userId}, #{userName}, #{userHead}, now(), now())")
void insertUserInfo(User req);
@Insert("DELETE FROM user WHERE userId = #{userId}")
int deleteUserInfoByUserId(String userId);
}
单元测试
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
private SqlSession sqlSession;
@Before
public void init() throws IOException {
// 1. 从SqlSessionFactory中获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
sqlSession = sqlSessionFactory.openSession();
}
@Test
public void test_insertUserInfo() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证
User user = new User();
user.setUserId("10002");
user.setUserName("小白");
user.setUserHead("1_05");
userDao.insertUserInfo(user);
logger.info("测试结果:{}", "Insert OK");
// 3. 提交事务
sqlSession.commit();
}
@Test
public void test_deleteUserInfoByUserId() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证
int count = userDao.deleteUserInfoByUserId("10002");
logger.info("测试结果:{}", count == 1);
// 3. 提交事务
sqlSession.commit();
}
@Test
public void test_updateUserInfo() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证
int count = userDao.updateUserInfo(new User(1L, "10001", "叮当猫"));
logger.info("测试结果:{}", count);
// 3. 提交事务
sqlSession.commit();
}
@Test
public void test_queryUserInfoById() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:基本参数
User user = userDao.queryUserInfoById(1L);
logger.info("测试结果:{}", JSON.toJSONString(user));
}
@Test
public void test_queryUserInfo() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:对象参数
User user = userDao.queryUserInfo(new User(1L));
logger.info("测试结果:{}", JSON.toJSONString(user));
}
@Test
public void test_queryUserInfoList() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:对象参数
List<User> users = userDao.queryUserInfoList();
logger.info("测试结果:{}", JSON.toJSONString(users));
}
}
运行新增和根据id查询测试一下,说明注解方式处理Sql成功啦,大家写完也可以测试其他的增删改查方法。
数据库数据