官方文档是这么写的
MyBatis是一款支持普通SQL查询、存储过程和高级映射的持久层框架。MyBatis消除了几乎所有的JDBC代码、参数的设置和结果集的检索。MyBatis可以使用简单的XML或注解用于参数配置和原始映射,将接口和Java POJO(普通Java对象)映射成数据库中的记录。
注意,这里空构造方法必须要有,SqlSession的selectOne方法查询一条信息的时候会调用空构造方法去实例化一个domain出来。
写config.xml文件
在写SQL语句之前,首先得有SQL运行环境,因此第一步是配置SQL运行的环境。
<?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>
<typeAlias alias="Student" type="com.xrq.domain.Student"/>
</typeAliases>
<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://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="student.xml"/>
</mappers>
</configuration>
在来一个student.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.xrq.StudentMapper">
<select id="selectStudentById" parameterType="int" resultType="Student">
<![CDATA[
select * from student where studentId = #{id}
]]>
</select>
</mapper>
引入两个jar包,一个是mybatis 和 MYSQL -JDBC的包
接着创建一个类,我起名字叫做StudentOperator,由于这种操作数据库的类被调用频繁但是调用者之间又不存在数据共享的问题,因此使用单例会比较节省资源。为了好看,写一个父类BaseOperator,SqlSessionFactory和Reader都定义在父类里面,子类去继承这个父类:
public class BaseOperator
{
protected static SqlSessionFactory ssf;
protected static Reader reader;
static
{
try
{
reader = Resources.getResourceAsReader("config.xml");
ssf = new SqlSessionFactoryBuilder().build(reader);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
然后创建子类StudentOperator:
public class StudentOperator extends BaseOperator
{
private static StudentOperator instance = new StudentOperator();
private StudentOperator()
{
}
public static StudentOperator getInstance()
{
return instance;
}
public Student selectStudentById(int studentId)
{
SqlSession ss = ssf.openSession();
Student student = null;
try
{
student = ss.selectOne("com.xrq.StudentMapper.selectStudentById", 1);
}
finally
{
ss.close();
}
return student;
}
}
这个类里面做了两件事情:
1、构造一个StudentOperator的单实例
2、写一个方法根据studentId查询出一个指定的Student
config.xml里的文件
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
typeAliases 就是别名的意思,以后找domain.blog.Blog 直接找Blog就好了.
<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://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
transactionManager
在MyBatis中有两种事物管理器类型,一种是JDBC,一种是MANAGED。
dataSource
为了方便使用使用延迟加载,数据源才是必须的
UNPOOLED 每次被请求时打开和关闭连接
POOLED JDBC连接对象的数据源连接池的实现
JNDI 实现是为了使用如Spring或应用服务器这类的容器
driver—-这是JDBC驱动的Java类的完全限定名
url—-这是数据库的JDBC URL地址
username—-登陆数据库的用户名
password—-登录数据库的密码
defaultTransactionIsolationLevel—-默认的连接事物隔离级别
driver.encoding—-传递数据库驱动的属性,前缀以”driver.”开头即可,”driver.encoding”表示的就是传递encoding属性
poolMaximumActiveConnections—-在任意时间存在的活动(也就是正在使用)连接的数量,默认值为10
poolMaximumIdleConnections—-任意时间存在的空闲连接数
poolMaximumCheckoutTime—-在被强制返回之前,池中连接被检查的时间,默认值为2000毫秒也就是20秒
poolTimeToWait、poolPingQuery、poolPingEnabled、poolPingConnectionsNotUsedFor—-这些都是一些侦测数据库连接的属性
initial_context—-这个属性用来从初始上下文寻找环境,这个是可选属性,如果被忽略,那么data_source属性将会直接以initialContext为背景再次寻找
data_source—-这是引用数据源实例位置的上下文路径,它会以由initial_context查询返回的环境为背景来查找,如果initial_context没有返回结果时,直接以初始上下文为环境来查找
// 使用相对于类路径的资源
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
// 使用完全限定路径
<mappers>
<mapper url="file:///var/sqlmaps/AuthorMapper.xml"/>
<mapper url="file:///var/sqlmaps/BlogMapper.xml"/>
<mapper url="file:///var/sqlmaps/PostMapper.xml"/>
</mappers>
settings
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="enhancementEnabled" value="false"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25000"/>
</settings>
```
为什么要用 `<![CDATA[ ... ]]>`
因为sql是写在xml里面的文件.加上这个是保证sql能正确编译
<div class="se-preview-section-delimiter"></div>
<select id="selectStudentByIdAndName" parameterType="Student" resultType="Student">
<![CDATA[
select * from student where studentId = #{studentId} and studentName = #{studentName};
]]>
</select>
使用参数的时候使用了”#”,另外还有一个符号”$”也可以引用参数,使用”#”最重要的作用就是防止SQL注入。
student = ss.selectOne("com.xrq.StudentMapper.selectStudentByIdAndName", new Student(studentId, studentName, 0, null));
<select id="selectAll" parameterType="int" resultType="Student" flushCache="false" useCache="true"
timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
<![CDATA[
select * from student where studentId > #{id};
]]>
</select>
flushCache—-将其设置为true,无论语句什么时候被调用,都会导致缓存被清空,默认值为false
useCache—-将其设置为true,将会导致本条语句的结果被缓存,默认值为true
timeout—-这个设置驱动程序等待数据库返回请求结果,并抛出异常事件的最大等待值,默认这个参数是不设置的(即由驱动自行处理)
fetchSize—-这是设置驱动程序每次批量返回结果的行数,默认不设置(即由驱动自行处理)
statementType—-STATEMENT、PREPARED或CALLABLE的一种,这会让MyBatis选择使用Statement、PreparedStatement或CallableStatement,默认值为PREPARED。这个相信大多数朋友自己写JDBC的时候也只用过PreparedStatement
resultSetType—-FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE中的一种,默认不设置(即由驱动自行处理)
list = ss.selectList("com.xrq.StudentMapper.selectAll", studentId);
使用resultMap来接收查询结果
<resultMap type="Student" id="studentResultMap">
<id property="studentId" column="studentId" />
<result property="studentName" column="studentName" />
<result property="studentAge" column="studentAge" />
<result property="studentPhone" column="studentPhone" />
</resultMap>
<select id="selectAll" parameterType="int" resultMap="studentResultMap" flushCache="false" useCache="true"
timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
<![CDATA[
select * from student where studentId > #{id};
]]>
</select>
1、resultMap定义中主键要使用id
2、resultMap和resultType不可以同时使用
<insert id="insertOneStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="studentId">
<![CDATA[
insert into student(studentName, studentAge, studentPhone)
values(#{studentName}, #{studentAge}, #{studentPhone});
]]>
</insert>
useGeneratedKeys表示让数据库自动生成主键,keyProperty表示生成主键的列。
<sql id="insertColumns">
studentName, studentAge, studentPhone
</sql>
然后在修改一下insert: 可以重用的
<insert id="insertOneStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="studentId">
insert into student(<include refid="insertColumns" />)
values(#{studentName}, #{studentAge}, #{studentPhone});
</insert>
动态SQL
<select id="selectInCondition" parameterType="student" resultType="student">
select * from student where studentId > #{studentId}
<if test="studentName != null">
and studentName = #{studentName};
</if>
</select>
<select id="selectInCondition" parameterType="student" resultType="student">
<![CDATA[
select * from student where studentId > #{studentId}
]]>
<if test="studentName != null and studentName != 'Jack' ">
and studentName = #{studentName}
</if>
<if test="studentAge != 0">
and studentAge = #{studentAge};
</if>
</select>
注意一下,能用”<![CDATA[ ... ]]>”
尽量还是用,不过只包动态SQL外的内容。
另外,test里面可以判断字符串、整型、浮点型,大胆地写判断条件吧。如果属性是复合类型,则可以使用A.B的方式去获取复合类型中的属性来进行比较。
choose、when、otherwise
<select id="selectInCondition" parameterType="student" resultType="student">
<![CDATA[
select * from student where studentId > #{studentId}
]]>
<choose>
<when test="studentName != null">
and studentName = #{studentName};
</when>
<when test="studentAge != 0">
and studentAge = #{studentAge};
</when>
<otherwise>
or 1 = 1;
</otherwise>
</choose>
</select>
两个when只能满足一个,都不满足则走other。还是注意一下这里的”<![CDATA[ ... ]]>
”,不可以包围整个语句。
<select id="selectInCondition" parameterType="student" resultType="student">
<![CDATA[
select * from student
]]>
<where>
<if test="studentName != null and studentName != 'Jack' ">
and studentName = #{studentName}
</if>
<if test="studentAge != 0">
and studentAge = #{studentAge};
</if>
</where>
</select>
where元素知道如果由被包含的标记返回任意内容,就仅仅插入where。而且,如果以”and”或”or”开头的内容,那么就会跳过where不插入。
<trim prefix="WHERE" prefixOverrides="AND |OR ">
…
</trim>
<trim prefix="SET" prefixOverrides=",">
…
</trim>
<select id="selectPostIn" resultType="domain.blog.Post">
<![CDATA[
SELECT * FROM POST P WHERE ID in
]]>
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!-- 注解配置 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<context:annotation-config />
<context:component-scan base-package="org.xrq" />
<!-- 数据库连接池,这里使用alibaba的Druid -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:config.xml" />
<property name="mapperLocations" value="classpath:*_mapper.xml" />
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
@Repository
public class StudentDaoImpl extends SqlSessionDaoSupport implements StudentDao
{
private static final String NAMESPACE = "StudentMapper.";
@Resource
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
{
super.setSqlSessionFactory(sqlSessionFactory);
}
public List<Student> selectAllStudents()
{
return getSqlSession().selectList(NAMESPACE + "selectAllStudents");
}
public int insertStudent(Student student)
{
return getSqlSession().insert(NAMESPACE + "insertStudent", student);
}
}
@Repository注解,对应的是持久层即Dao层,其作用是直接和数据库交互,通常来说一个方法对应一条具体的Sql语句
@Service注解,对应的是服务层即Service层,其作用是对单条/多条Sql语句进行组合处理,当然如果简单的话就直接调用Dao层的某个方法了
@Controller注解,对应的是控制层即MVC设计模式中的控制层,其作用是接收用户请求,根据请求调用不同的Service取数据,并根据需求对数据进行组合、包装返回给前端
@Component注解,这个更多对应的是一个组件的概念,如果一个Bean不知道属于拿个层,可以使用@Component注解标注
@Resource,这个注解和@Autowired注解是一个意思,都可以自动注入属性属性。由于SqlSessionFactory是MyBatis的核心,它在spring.xml中又进行过了声明,因此这里通过@Resource注解将id为”sqlSessionFactory”的Bean给注入进来,之后就可以通过getSqlSession()方法获取到SqlSession并进行数据的增、删、改、查了。
public class StudentTest
{
public static void main(String[] args)
{
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
StudentDao studentDao = (StudentDao)ac.getBean("studentDaoImpl");
Student student = new Student();
student.setStudentName("Lucy");
int j = studentDao.insertStudent(student);
System.out.println("j = " + j + "\n");
System.out.println("-----Display students------");
List<Student> studentList = studentDao.selectAllStudents();
for (int i = 0, length = studentList.size(); i < length; i++)
System.out.println(studentList.get(i));
}
}
单表事务管理
1.使用MyBatis的批量插入功能
2.使用Spring管理事务,任何一条数据插入失败
<insert id="batchInsert" useGeneratedKeys="true" parameterType="java.util.List">
<selectKey resultType="int" keyProperty="studentId" order="AFTER">
SELECT
LAST_INSERT_ID()
</selectKey>
insert into student(student_id, student_name) values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.studentId, jdbcType=INTEGER}, #{item.studentName, jdbcType=VARCHAR})
</foreach>
</insert>
public int batchInsertStudents(List<Student> studentList)
{
return getSqlSession().insert(NAMESPACE + "batchInsert", studentList);
}
多库/多表事务管理
(1)@Service注解
严格地说这里使用@Service注解不是特别好,因为Service作为服务层,更多的是应该对同一个Dao中的多个方法进行组合,如果要用到多个Dao中的方法,建议应该是放到Controller层中,引入两个Service,这里为了简单,就简单在一个Service中注入了StudentDao和TeacherDao两个了。
(2)@Transactional注解
这个注解用于开启事务管理,注意@Transactional注解的使用前提是该方法所在的类是一个Spring Bean,因此(1)中的@Service注解是必须的。换句话说,假如你给方法加了@Transactional注解却没有给类加@Service、@Repository、@Controller、@Component四个注解其中之一将类声明为一个Spring的Bean,那么对方法的事务管理,是不会起作用的。关于@Transactional注解,会在下面进一步解读。
(1) @Transactional(propagation = Propagation.REQUIRED)
最重要的先说,propagation属性表示的是事务的传播特性,一共有以下几种:
因此我们可以来简单分析一下上面的insertTeacherAndStudent方法:
由于没有指定propagation属性,因此事务传播特性为默认的REQUIRED
StudentDao的insertStudent方法先运行,此时没有事务,因此新建一个事务
TeacherDao的insertTeacher方法接着运行,此时由于StudentDao的insertStudent方法已经开启了一个事务,insertTeacher方法加入到这个事务中
StudentDao的insertStudent方法和TeacherDao的insertTeacher方法组成了一个事务,两个方法要么同时执行成功,要么同时执行失败
(2)@Transactional(isolation = Isolation.DEFAULT)
事务隔离级别,这个不细说了,可以参看事务及事务隔离级别一文。
(3)@Transactional(readOnly = true)
该事务是否为一个只读事务,配置这个属性可以提高方法执行效率。
(4)@Transactional(rollbackFor = {ArrayIndexOutOfBoundsException.class, NullPointerException.class})
遇到方法抛出ArrayIndexOutOfBoundsException、NullPointerException两种异常会回滚数据,仅支持RuntimeException的子类。
(5)@Transactional(noRollbackFor = {ArrayIndexOutOfBoundsException.class, NullPointerException.class})
这个和上面的相反,遇到ArrayIndexOutOfBoundsException、NullPointerException两种异常不会回滚数据,同样也是仅支持RuntimeException的子类。对(4)、(5)不是很理解的朋友,我给一个例子:
@Transactional(rollbackForClassName = {"NullPointerException"})
public void insertTeacherAndStudent(Teacher teacher, Student student)
{
studentDao.insertStudent(student);
teacherDao.insertTeacher(teacher);
String s = null;
s.length();
}
构造Student、Teacher的数据运行一下,然后查看下库里面有没有对应的记录就好了,然后再把rollbackForClassName改为noRollbackForClassName,对比观察一下。
(6)@Transactional(rollbackForClassName = {“NullPointerException”})
、
@Transactional(noRollbackForClassName = {“NullPointerException”})
这两个放在一起说了,和上面的(4)、(5)差不多,无非是(4)、(5)是通过.class来指定要回滚和不要回滚的异常,这里是通过字符串形式的名字来制定要回滚和不要回滚的异常。
(7)@Transactional(timeout = 30)
事务超时时间,单位为秒。
(8)@Transactional(value = “tran_1″)
value这个属性主要就是给某个事务一个名字而已,这样在别的地方就可以使用这个事务的配置。