预研报告——MyBatis持久层的demo
一、预研任务介绍和预研目标
任务介绍:
与 Hibernate 相比, MyBatis 是一个半自动化的持久层框架,以轻量级、效率高、原生代而好评如潮。虽然有在分享会上大致讲解,但是还是重新梳理成文字,方便后来人查阅。
预研目标:
编写并讲解 MyBatis 与持久层结合的 demo ,实际应用起这门新技术。
二、操作步骤
1. jar 包准备
备注:mybatis.jar是mybatis的核心,mybatis-spring是mybatis团队出品的mybatis整合spring工具包。
2. 准备工作
1 ) 在数据库(MySQL )上的 test 数据库新建如下表格并添加测试数据:
CREATE TABLE sys_employees ( emp_id INT(11) NOT NULL AUTO_INCREMENT, emp_name VARCHAR(255) NULL DEFAULT NULL COMMENT '员工名称', emp_password VARCHAR(255) NULL DEFAULT NULL COMMENT '员工密码', emp_email VARCHAR(255) NULL DEFAULT NULL COMMENT '员工邮件', emp_desc VARCHAR(255) NULL DEFAULT NULL COMMENT '员工描述', emp_account VARCHAR(255) NULL DEFAULT NULL COMMENT '员工账户', is_sys BOOL NULL DEFAULT '0' COMMENT '是否管理员 0:否 1:是', is_using BOOL NULL DEFAULT '1' COMMENT '是否在职 0:否 1:是', PRIMARY KEY(emp_id) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;Insert into sys_employees values(null, 'jayzee', '123456', null, null, 'jayzee', 0, 1);Insert into sys_employees values(null, 'nikey', '123456', null, null, 'jayzee', 0, 1);
2 ) 新建一个简单的java项目 Pro 4 - MyBatisDemo,在其 build path 下新建一个source folder 名为 resources 专门放配置文件,在resources文件夹 下编写如下文件( generator.xml )并使用 mybatis generator 插件 [1]生成 model 类、 mapper 接口及 xml :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- classPathEntry:数据库的JDBC驱动 --> <classPathEntry location="/home/jayzeee/Documents/Java/Jars/mysql/mysql-connector-java-5.1.17-bin.jar" /> <context id="MySQLTables" targetRuntime="MyBatis3"> <!-- 去除自动生成的注释 --> <commentGenerator> <property name="suppressAllComments" value="true" /> </commentGenerator> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/test" userId="root" password="123456"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- targetProject:自动生成代码的位置 --> <javaModelGenerator targetPackage="com.nikey.oa.model.Demo" targetProject="Pro 4 - MyBatisDemo"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <sqlMapGenerator targetPackage="com.nikey.oa.mapper.Demo" targetProject="Pro 4 - MyBatisDemo"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <javaClientGenerator type="XMLMAPPER" targetPackage="com.nikey.oa.mapper.Demo" targetProject="Pro 4 - MyBatisDemo"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- tableName:用于自动生成代码的数据库表;domainObjectName:对应于数据库表的javaBean类名 --> <table tableName="sys_employees" domainObjectName="Employee" /> </context> </generatorConfiguration>
3 ) 删除自动生成的 Employee-example.java 和 EmployeeMapper.java 里面的所有方法; EmployeeMapper.xml 中 <resultMap> 之后的文本全部不要(自动生成的这些东西太冗杂了,我们完全无用,自己的才是最合适的)。还有 一个建议,最好在resources下面新建一个与 src存放mapper 相同的包名把把 EmployeeMapper.xml迁移过去(后面会解释为什么这么做)。目前包结构如下:
4) 在 resources 下添加 log4j.properties ,开启 mybatis 后台信息打印,内容如下:
# Rules reminder: # DEBUG < INFO < WARN < ERROR < FATAL # Global logging configuration log4j.rootLogger=DEBUG, stdout # My logging configuration... log4j.logger.org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl=stdout ## Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
3. 整合 Spring
1) resources 下新建 spring 文件夹,并新建 applicationContext.xml ,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright 2013 Nikey Author Jayzee Created 2013-08-04 --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 启用autowire --> <context:annotation-config /> <!-- 启用spring注解扫描并指定包所在的位置 --> <context:component-scan base-package="*" /> <!--配置apache dbcp数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="123456" /> <!-- 连接池启动时的初始值 --> <property name="initialSize" value="7" /> <!-- 连接池的最大值 --> <property name="maxActive" value="20" /> <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --> <property name="maxIdle" value="7" /> <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 --> <property name="minIdle" value="2" /> </bean> <!-- 使用事务管理器管理数据源 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 启用事务注解,使用@Transactional注解 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 定义mybatis的sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 以每个类的类名作为该类的别名,如Employee等同于com.nikey.oa.model.Demo.Employee --> <property name="typeAliasesPackage" value="com.nikey.oa.model" /> <!--configLocation属性指定mybatis的核心配置文件--> <property name="configLocation" value="classpath:mybatis/configuration.xml"/> </bean> <!-- sqlsessiontemplate模板,主要用于测试 --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> <!-- 扫描mybatis的mappers并让他们自动注入, 为什么上面建议放配置文件的包名与放接口的包名一致,是因为这个扫描器会把xml和interface一起扫描,并将xml的内容作为interface的实现类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.nikey.oa.mapper" /> </bean> </beans>
2) mybatis核心配置文件配置
<?xml version="1.0" encoding="utf8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 配置全局设置,参照API --> <settings> <!-- 开启batch --> <setting name="defaultExecutorType" value="BATCH" /> </settings> <!-- 配置别名,已在spring配置,无需重复配置 --> <!-- <typeAliases> <typeAlias alias="Employee" type="com.nikey.oa.model.Employee"/> </typeAliases> --> <!-- 由于已在spring配置文件里扫描mappper,无需重复配置 --> <!-- <mappers> <mapper resource="classpath:com/nikey/oa/mapper/Demo/EmployeeMapper.xml"/> </mappers> --> <!-- 添加对一些特殊类型的处理,详情参照API --> <typeHandlers> <typeHandler handler="org.apache.ibatis.type.BooleanTypeHandler" javaType="Boolean" jdbcType="BIT"/> <typeHandler handler="org.apache.ibatis.type.BlobTypeHandler" javaType="byte[]" jdbcType="BLOB"/> </typeHandlers> </configuration>
3) mybatis的核心,mapper(即dao)的讲解以及使用
首先,打开EmployeeMapper.java,修改内容如下:
package com.nikey.oa.mapper.Demo; import com.nikey.oa.model.Demo.Employee; /** * @author jayzeee * */ public interface EmployeeMapper {/** * @param employee * @return 根据查询返回一个雇员 */ Employee getAnEmployee(Employee employee); }
其次,修改EmployeeMapper.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="com.nikey.oa.mapper.Demo.EmployeeMapper" > <!-- id表示主键,result表示普通字段,column与数据库字段名对应,property与javabean的属性对应,type是javabean名字,其实我们这里我们已经使用了别名, 它的全名是com.nikey.oa.model.Demo.Employee --> <resultMap id="BaseResultMap" type="Employee" > <id column="emp_id" property="empId" jdbcType="INTEGER" /> <result column="emp_name" property="empName" jdbcType="VARCHAR" /> <result column="emp_password" property="empPassword" jdbcType="VARCHAR" /> <result column="emp_email" property="empEmail" jdbcType="VARCHAR" /> <result column="emp_desc" property="empDesc" jdbcType="VARCHAR" /> <result column="emp_account" property="empAccount" jdbcType="VARCHAR" /> <result column="is_sys" property="isSys" jdbcType="BIT" /> <result column="is_using" property="isUsing" jdbcType="BIT" /> </resultMap> <!-- sql块,方便重用 --> <sql id="employee"> E.emp_id, E.emp_name, E.emp_password, E.emp_email, E.emp_desc, E.emp_account, E.is_sys, E.is_using </sql> <!-- id与方法同名,parameterType表示传进来的参数(可以不写让mybatis自动判断),resultmap表示返回的结果集, 我们可以自己灵活组织结果集,学mybatis实际就是在写sql和组织resultmap --> <select id="getAnEmployee" parameterType="Employee" resultMap="BaseResultMap"> select <!-- 引用sql块 --> <include refid="employee"/> from sys_employees E <!-- 动态判断,不为空则加入where后面,where有自动去除and的功能,所以不用担心语句出错 --> <where> <if test="empId !=null "> E.emp_id = #{empId} </if> <if test="empName !=null and empName != '' "> and E.emp_name = #{empName} </if> <if test="empPassword !=null and empPassword != '' "> and E.emp_password = #{empPassword} </if> </where> </select> </mapper>
4)测试
首先,在build path下新建source文件夹test,并在test下新建包名:com.nikey.oa.mapper.Demo
其次,我们在该包下建一个SqlSessionTemplateTest.java,内容如下:
package com.nikey.oa.mapper.Demo; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.nikey.oa.model.Demo.Employee; /** * @author jayzeee * */ public class SqlSessionTemplateTest {public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring/applicationContext.xml"); SqlSessionTemplate sqlSessionTemplate = (SqlSessionTemplate) context.getBean("sqlSessionTemplate"); //使用sqlSessionTemplate调用mapper里的方法,语法:无需指定mapper,直接在参数里填方法名,因为这个方法名是全局的 Employee e = new Employee(); e.setEmpName("jayzee"); Employee employee = sqlSessionTemplate.selectOne("getAnEmployee", e); System.out.println(employee); } }
打印结果:com.nikey.oa.model.Demo.Employee@1d5e5d7
至此,我们的包结构如下:
如果你想开启mybatis的缓存机制,需要加入如下两个配置:
配置1:在EmployeeMapper.xml下添加<cache>
配置2:系列化Employee.java
入门讲解到此为止,下面讲解mybatis的高级部分
4.灵活传参以及resultmap的组织
1) 准备工作
在本地的mysql的test数据库下运行下面的语句:
CREATE TABLE sys_roles ( role_id INT(11) NOT NULL AUTO_INCREMENT, role_name VARCHAR(255) NULL DEFAULT NULL COMMENT '角色名称', role_desc VARCHAR(255) NULL DEFAULT NULL COMMENT '角色描述', is_enable BOOL NULL DEFAULT '1' COMMENT '是否启用 0:否 1:是', is_sys BOOL NULL DEFAULT '0' COMMENT '是否管理员 0:否 1:是', PRIMARY KEY(role_id) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE sys_employees_roles ( emp_id INT(11) NOT NULL COMMENT '员工', role_id INT(11) NOT NULL COMMENT '角色', PRIMARY KEY(emp_id, role_id), INDEX emp_id_index(emp_id), INDEX role_id_index(role_id), FOREIGN KEY(emp_id) REFERENCES sys_employees(emp_id) ON DELETE NO ACTION ON UPDATE CASCADE, FOREIGN KEY(role_id) REFERENCES sys_roles(role_id) ON DELETE NO ACTION ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE departments ( depart_id INT(11) NOT NULL AUTO_INCREMENT, depart_name VARCHAR(255) NULL DEFAULT NULL COMMENT '部门名称', depart_desc VARCHAR(255) NULL DEFAULT NULL COMMENT '部门描述', PRIMARY KEY(depart_id) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE departments_employees ( emp_id INT(11) NOT NULL COMMENT '员工', depart_id INT(11) NOT NULL COMMENT '部门', PRIMARY KEY(emp_id, depart_id), INDEX depart_id_index(depart_id), INDEX emp_id_index(emp_id), FOREIGN KEY(depart_id) REFERENCES departments(depart_id) ON DELETE NO ACTION ON UPDATE CASCADE, FOREIGN KEY(emp_id) REFERENCES sys_employees(emp_id) ON DELETE NO ACTION ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;insert into sys_roles values (4, '技术工程师', '小弟', 1, 0);insert into sys_roles values (5, '软件工程师', '小弟', 1, 0);insert into sys_employees_roles values (1, 4);insert into sys_employees_roles values (1, 5);insert into departments values (1, '软件部', '做软件的');insert into departments_employees values (1, 1);
修改generator.xml底部内容如下:
<!-- tableName:用于自动生成代码的数据库表;domainObjectName:对应于数据库表的javaBean类名 --> <table tableName="sys_roles" domainObjectName="Role" /> <table tableName="departments" domainObjectName="Department" />
参照上文删除-example.java文件和清除接口以及xml的无用信息
在Employee.java下添加如下信息:
private List<Role> roles; private Department department; public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; }