前言
之前一段时间写了【Spring源码分析】系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章【MyBatis源码分析】,在【MyBatis源码分析】文章的基础之上,可以继续分析数据库连接池、Spring整合MyBatis源码、Spring事物管理tx等等。
【MyBatis源码分析】整个文章结构相较【Spring源码分析】稍微改一改,后者会在每一部分源码分析的开头列出要分析的源码的实例,比如:
- 分析Bean流程加载,就会先写Bean的代码示例及xml中配置Bean的示例
- 分析AOP流程,就会先写AOP的代码及xml中配置AOP的示例
【MyBatis源码分析】系列文章,在本文中会一次性地将所有的代码示例写完,之后就针对这些代码一部分一部分进行分析,探究MyBatis原理。
其实MyBatis代码示例,我在之前的文章里面记得至少写了两遍,完全可以拿之前的文章作为例子,但是这里要再写一遍,就希望分享给网友朋友们一点态度:作为一个程序员,还是应当多去写代码,多去实践,不要认为之前写过的东西就没必要再写一遍,之前懂的内容就没必要再学习一遍,温故知新,写得越多用得越熟练,思考得越多成长越快。
SQL准备
首先还是建表,这里准备一段SQL:
1 drop table if exists mail; 2 3 create table mail 4 ( 5 id int auto_increment not null comment ‘主键id‘, 6 create_time datetime not null comment ‘创建时间‘, 7 modify_time timestamp not null comment ‘修改时间‘, 8 web_id int not null comment ‘站点id,1表示新浪,2表示QQ,3表示搜狐,4表示火狐‘, 9 mail varchar(50) not null comment ‘邮箱名‘, 10 use_for varchar(30) comment ‘邮箱用途‘, 11 primary key(id), 12 index use_for(use_for), 13 unique index web_id_mail(web_id, mail) 14 )charset=utf8 engine=innodb comment=‘邮箱表‘;
很多人可能有不止一个邮箱,新浪的、腾讯的、搜狐的,每个邮箱可能又有不一样的用途,这里就拿邮箱做一个例子。
建立每张表的时候应当注意唯一约束,像这里,一个网站下的邮箱一定是唯一的,不可能在新浪下同时存在两个名为"[email protected]"的邮箱名,因此对web_id+mail做唯一索引。
建立实体类
建立完毕SQL之后,第二步一定是为表建立在Java层面的实体类,在SQL层面不同的词语使用"_"分割,在Java层面不同的词语则使用驼峰命名法:
- 对于类名/接口名/枚举类,使用首字母大写的驼峰命名法
- 对于字段,使用首字母小写的驼峰命名法
现在为mail表建立实体类:
1 public class Mail { 2 3 /** 4 * 主键id 5 */ 6 private long id; 7 8 /** 9 * 创建时间 10 */ 11 private Date createTime; 12 13 /** 14 * 修改时间 15 */ 16 private Date modifyTime; 17 18 /** 19 * 网站id,1表示新浪,2表示QQ,3表示搜狐,4表示火狐 20 */ 21 private int webId; 22 23 /** 24 * 邮箱 25 */ 26 private String mail; 27 28 /** 29 * 用途 30 */ 31 private String useFor; 32 33 public Mail() { 34 35 } 36 37 public Mail(int webId, String mail, String useFor) { 38 this.webId = webId; 39 this.mail = mail; 40 this.useFor = useFor; 41 } 42 43 44 45 public long getId() { 46 return id; 47 } 48 49 public void setId(long id) { 50 this.id = id; 51 } 52 53 public Date getCreateTime() { 54 return createTime; 55 } 56 57 public void setCreateTime(Date createTime) { 58 this.createTime = createTime; 59 } 60 61 public Date getModifyTime() { 62 return modifyTime; 63 } 64 65 public void setModifyTime(Date modifyTime) { 66 this.modifyTime = modifyTime; 67 } 68 69 public int getWebId() { 70 return webId; 71 } 72 73 public void setWebId(int webId) { 74 this.webId = webId; 75 } 76 77 public String getMail() { 78 return mail; 79 } 80 81 public void setMail(String mail) { 82 this.mail = mail; 83 } 84 85 public String getUseFor() { 86 return useFor; 87 } 88 89 public void setUseFor(String useFor) { 90 this.useFor = useFor; 91 } 92 93 @Override 94 public String toString() { 95 return "MailDO [id=" + id + ", createTime=" + createTime + ", modifyTime=" + modifyTime + ", webId=" + webId + ", mail=" + mail + ", useFor=" 96 + useFor + "]"; 97 } 98 99 }
注意实体类一定要重写toStirng()方法,便于定位问题。
建立数据访问层
下一步,个人喜好是建立数据访问层,对于数据访问层通常有如下约定:
- 数据访问层使用Dao命名,它定义了对表的基本增删改查操作
- 数据访问层之上使用Service命名,它的作用是对于数据库的多操作进行组合,比如先查再删、先删再增、先改再查再删等等,这些操作不会放在Dao层面去操作,而会放在Service层面去进行组合
那么,首先定义一个MailDao,我定义增删改查五个方法,其中查询两个方法,一个查单个,一个查列表:
1 public interface MailDao { 2 3 /** 4 * 插入一条邮箱信息 5 */ 6 public long insertMail(Mail mail); 7 8 /** 9 * 删除一条邮箱信息 10 */ 11 public int deleteMail(long id); 12 13 /** 14 * 更新一条邮箱信息 15 */ 16 public int updateMail(Mail mail); 17 18 /** 19 * 查询邮箱列表 20 */ 21 public List<Mail> selectMailList(); 22 23 /** 24 * 根据主键id查询一条邮箱信息 25 */ 26 public Mail selectMailById(long id); 27 28 }
接着是Dao的实现类,通常以"Impl"结尾,"Impl"是关键字"Implements"的缩写,表示接口实现类的意思。MailDao的实现类就命名为MailDaoImpl了,代码为:
1 public class MailDaoImpl implements MailDao { 2 3 private static final String NAME_SPACE = "MailMapper."; 4 5 private static SqlSessionFactory ssf; 6 7 private static Reader reader; 8 9 static { 10 try { 11 reader = Resources.getResourceAsReader("mybatis/config.xml"); 12 ssf = new SqlSessionFactoryBuilder().build(reader); 13 } 14 catch (IOException e) { 15 e.printStackTrace(); 16 } 17 } 18 19 @Override 20 public long insertMail(Mail mail) { 21 SqlSession ss = ssf.openSession(); 22 try { 23 int rows = ss.insert(NAME_SPACE + "insertMail", mail); 24 ss.commit(); 25 if (rows > 0) { 26 return mail.getId(); 27 } 28 return 0; 29 } catch (Exception e) { 30 ss.rollback(); 31 return 0; 32 } finally { 33 ss.close(); 34 } 35 } 36 37 @Override 38 public int deleteMail(long id) { 39 SqlSession ss = ssf.openSession(); 40 try { 41 int rows = ss.delete(NAME_SPACE + "deleteMail", id); 42 ss.commit(); 43 return rows; 44 } catch (Exception e) { 45 ss.rollback(); 46 return 0; 47 } finally { 48 ss.close(); 49 } 50 } 51 52 @Override 53 public int updateMail(Mail mail) { 54 SqlSession ss = ssf.openSession(); 55 try { 56 int rows = ss.update(NAME_SPACE + "updateMail", mail); 57 ss.commit(); 58 return rows; 59 } catch (Exception e) { 60 ss.rollback(); 61 return 0; 62 } finally { 63 ss.close(); 64 } 65 } 66 67 @Override 68 public List<Mail> selectMailList() { 69 SqlSession ss = ssf.openSession(); 70 try { 71 return ss.selectList(NAME_SPACE + "selectMailList"); 72 } finally { 73 ss.close(); 74 } 75 } 76 77 @Override 78 public Mail selectMailById(long id) { 79 SqlSession ss = ssf.openSession(); 80 try { 81 return ss.selectOne(NAME_SPACE + "selectMailById", id); 82 } finally { 83 ss.close(); 84 } 85 } 86 87 }
具体代码就不看了,会在第二篇文章开始分析。
建立MyBatis配置文件
接着就是建立MyBatis的配置文件了,MyBatis的配置文件有两个,一个是环境的配置config.xml,一个是具体SQL的编写mail.xml。首先看一下config.xml:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 4 5 <configuration> 6 7 <typeAliases> 8 <typeAlias alias="Mail" type="org.xrq.mybatis.pojo.Mail"/> 9 </typeAliases> 10 11 <environments default="development"> 12 <environment id="development"> 13 <transactionManager type="JDBC"/> 14 <dataSource type="POOLED"> 15 <property name="driver" value="com.mysql.jdbc.Driver"/> 16 <property name="url" value="jdbc:mysql://localhost:3306/test"/> 17 <property name="username" value="root"/> 18 <property name="password" value="root"/> 19 </dataSource> 20 </environment> 21 </environments> 22 23 <mappers> 24 <mapper resource="mybatis/mail.xml"/> 25 </mappers> 26 27 </configuration>
接着是编写SQL语句的mail.xml:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 5 <mapper namespace="MailMapper"> 6 7 <resultMap type="Mail" id="MailResultMap"> 8 <result column="id" property="id" /> 9 <result column="create_time" property="createTime" /> 10 <result column="modify_time" property="modifyTime" /> 11 <result column="web_id" property="webId" /> 12 <result column="mail" property="mail" /> 13 <result column="use_for" property="useFor" /> 14 </resultMap> 15 16 <sql id="fields"> 17 id, create_time, modify_time, web_id, mail, use_for 18 </sql> 19 20 <sql id="fields_value"> 21 null, now(), now(), #{webId}, #{mail}, #{useFor} 22 </sql> 23 24 <insert id="insertMail" parameterType="Mail" useGeneratedKeys="true" keyProperty="id"> 25 insert into mail( 26 <include refid="fields" /> 27 ) values( 28 <include refid="fields_value" /> 29 ); 30 </insert> 31 32 <delete id="deleteMail" parameterType="java.lang.Long"> 33 delete from mail where id = #{id}; 34 </delete> 35 36 <update id="updateMail" parameterType="Mail"> 37 update mail 38 <set> 39 <if test="web_id != 0"> 40 web_id = #{webId} 41 </if> 42 <if test="mail != null"> 43 mail = #{mail} 44 </if> 45 <if test="use_for != null"> 46 use_for = #{useFor} 47 </if> 48 </set> 49 where id = #{id}; 50 </update> 51 52 <select id="selectMailList" resultMap="MailResultMap"> 53 select <include refid="fields" /> from mail; 54 </select> 55 56 <select id="selectMailById" resultMap="MailResultMap" parameterType="java.lang.Long"> 57 select <include refid="fields" /> from mail where id = #{id}; 58 </select> 59 60 </mapper>
这个mail.xml我尽量写得全一点,这样后面分析的时候都会有代码示例,mail.xml中包括:
- resultMap
- <sql>标签
- 插入主键返回主键id
- 动态sql
建立单元测试代码
软件的正确性离不开良好的测试,通常测试有两种方式:
- 写main函数,这种方式我基本不使用,除非是测试一个很小的功能点比如Math.round这种,这种代码写完我也会直接删除的,不会留着提交到代码库上
- 使用单元测试工具比如junit,这是我常用的方式
其实很多公司的JD上面也有写着"能编写良好的单元测试代码",跑main函数的方式我个人真的是不太推荐。
接着看一下单元测试代码:
1 public class TestMyBatis { 2 3 private static MailDao mailDao; 4 5 static { 6 mailDao = new MailDaoImpl(); 7 } 8 9 @Test 10 public void testInsert() { 11 Mail mail1 = new Mail(1, "[email protected]", "个人使用"); 12 Mail mail2 = new Mail(2, "[email protected]", "企业使用"); 13 Mail mail3 = new Mail(3, "[email protected]", "注册账号使用"); 14 System.out.println(mailDao.insertMail(mail1)); 15 System.out.println(mailDao.insertMail(mail2)); 16 System.out.println(mailDao.insertMail(mail3)); 17 } 18 19 @Test 20 public void testDelete() { 21 System.out.println(mailDao.deleteMail(1)); 22 } 23 24 @Test 25 public void testUpdate() { 26 Mail mail = new Mail(2, "[email protected]", "个人使用"); 27 mail.setId(2); 28 System.out.println(mailDao.updateMail(mail)); 29 System.out.println(mailDao.selectMailById(2)); 30 } 31 32 @Test 33 public void testSelectOne() { 34 System.out.println(mailDao.selectMailById(2)); 35 } 36 37 @Test 38 public void testSelectList() { 39 List<Mail> mailList = mailDao.selectMailList(); 40 if (mailList != null && mailList.size() != 0) { 41 for (Mail mail : mailList) { 42 System.out.println(mail); 43 } 44 } 45 } 46 47 }
正确的情况下,应当五个方法跑出来全部是绿色的进度条。
当然,单元测试也可以单独跑每一个,我个人使用Eclipse/MyEclipse,都是支持的,相信其他IDE肯定也是支持这个功能的。