瞎聊
今天跟着参考书目尝试书写第一个MyBatis案例,跟着资料一路往下写,最后的运行结果却很是诧异,没有报任何错误,但是也没有任何结果。经过多次排查和修改,最终完成了整个项目。
通过这次修改过程,发现了之前没有注意到的一个编程恶习:对异常处理不完整,包括对异常进行捕获时,没有使用Exception作最后的捕获,最不能原谅的是捕获异常之后,竟然没有做任何处理,包括对异常信息的显示。正是因为这两个原因,导致问题的定位耗时严重。
除此之外,由于缺乏对XML的基本认识,导致配置文件出现了一些本不应该出现的问题,比如DTD声明错误以及 & 字符没有进行转义等低级错误。
不过通过这次改错的过程,对MyBatis的运行过程的理解更近了一步,还是很开心!
现在将项目重置到初始状态,再现修改过程,以为前车之鉴。
目录结构
血泪调试过程
为了方便叙述,先给出所有代码内容(代码内容来源于《JavaEE互联网轻量级框架整合开发》第三章实例)。
Role.java
package cn.edu.sxau.zpc.pojo;
public class Role {
private int id;
private String roleName;
private String note;
/**
* <p>Title: </p>
* <p>Description: </p>
*/
public Role() {
// TODO Auto-generated constructor stub
}
// 省略Getters and Setters
}
RoleMapper.java
package cn.edu.sxau.zpc.mapper;
import java.util.List;
import cn.edu.sxau.zpc.pojo.Role;
public interface RoleMapper {
public int insertRole(Role role);
public int deleteRole(int id);
public int updateRole(Role role);
public Role getRole(int id);
public List<Role> findRoles(String roleName);
}
RoleMapper.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="cn.edu.sxau.zpc.mapper.RoleMapper">
<insert id="insertRole" parameterType="role">
insert into t_role(role_name, note) values(#{roleName}, #{note})
</insert>
<delete id="deleteRole" parameterType="int">
delete from t_role where id = #{id}
</delete>
<update id="updateRole" parameterType="role">
update t_role set role_name = #{roleName}, note = #{note} where id = #{id}
</update>
<select id="getRole" parameterType="int">
select id, role_name as roleName, note from t_role
where id = #{id}
</select>
<select id="findRoles" parameterType="string" resultType="role">
select id, role_name as roleName, note from t_role
where role_name like concat('%', #{roleName}, '%')
</select>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 别名 -->
<typeAliases>
<typeAlias type="cn.edu.sxau.zpc.pojo.Role" alias="role"/>
</typeAliases>
<!-- 数据库环境 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=FALSE&serverTimezone=UTC&characterEncoding=utf8"/>
<property name="username" value="admin"/>
<property name="password" value="12346"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="cn/edu/sxau/zpc/mapper/RoleMapper.xml"/>
</mappers>
</configuration>
SqlSessionFactoryUtils.java
package cn.edu.sxau.zpc.utils;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionFactoryUtils {
private final static Class<SqlSessionFactoryUtils> LOCK = SqlSessionFactoryUtils.class;
private static SqlSessionFactory sqlSessionFactory = null;
private SqlSessionFactoryUtils() {
}
public static SqlSessionFactory getSqlSessionFactory() {
synchronized (LOCK) {
if (sqlSessionFactory != null) {
return sqlSessionFactory;
}
String resource = "mybatis-config.xml";
InputStream inputStream;
try {
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return sqlSessionFactory;
}
}
public static SqlSession openSqlSession() {
if (null == sqlSessionFactory) {
getSqlSessionFactory();
}
return sqlSessionFactory.openSession();
}
}
Main.java
package cn.edu.sxau.zpc.main;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import cn.edu.sxau.zpc.mapper.RoleMapper;
import cn.edu.sxau.zpc.pojo.Role;
import cn.edu.sxau.zpc.utils.SqlSessionFactoryUtils;
public class Main {
public static void main(String[] args) {
Logger log = Logger.getLogger(Main.class);
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtils.openSqlSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1);
log.info(role.getRoleName());
} catch (Exception e) {
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
运行项目,控台没有输入任何信息。
手动debug,通过在代码中到处穿插System.out.println("in here");
,最终将问题锁定在SqlSessionFactoryUtils中创建SqlSessionFactory的那个地方,即sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
既然找到了问题的出处,就开始排查,找了很长时间都没有找到根源,最后突发奇想,将catch中的IOException捕获放大到Exception捕获,控制台终于报出来错误信息。
try {
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
return null;
}
摘取有效报错信息:
Caused by: org.xml.sax.SAXParseException; lineNumber: 3; columnNumber: 16; 文档根元素 "configuration" 必须匹配 DOCTYPE 根 "mapper"。
根据报错信息发现问题是mybatis-config.xml文件的DTD声明有问题
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
正确格式应该是:
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
修改完DTD定义之后再次运行,发现还是有错误,不过好在错误原因报的很明确
Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 15; columnNumber: 96; 对实体 "serverTimezone" 的引用必须以 ';' 分隔符结尾。
要奔溃的感觉,因为我的数据库是MySQL8.0,连接的时候url附带信息较多,但是在XML中竟然没有对&符号进行转义。。。
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=FALSE&serverTimezone=UTC&characterEncoding=utf8"/>
正确格式应该是:
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=FALSE&serverTimezone=UTC&characterEncoding=utf8"/>
经过上面的修改,控制台终于打印出日志信息,但是总感觉哪里有些不对劲,于是试着打印一下role对象的信息,System.out.println(role.getNote());
,果然,程序都没有执行到这里。我竟然在捕获了异常的情况下没有打印异常信息。。。
修改后Main.java的main方法如下:
public static void main(String[] args) {
Logger log = Logger.getLogger(Main.class);
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtils.openSqlSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1);
System.out.println(role.getNote());
log.info(role.getRoleName());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
再次运行程序,得到下面的报错信息:
Error querying database.
Cause: org.apache.ibatis.executor.ExecutorException:
A query was run and no Result Maps were found for the Mapped Statement 'cn.edu.sxau.zpc.mapper.RoleMapper.getRole'. It's likely that neither a Result Type nor a Result Map was specified.
好吧,RoleMapper.xml的getRole的select标记中忘了写resultType属性了,修改一下:
<select id="getRole" parameterType="int" resultType="role">
select id, role_name as roleName, note from t_role
where id = #{id}
</select>
终于,一切正常了,日志反馈信息也与数据库信息吻合。
DEBUG 2019-04-29 20:39:04,319 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1
INFO 2019-04-29 20:39:04,321 cn.edu.sxau.zpc.main.Main: enenene