MyBatis 实践 -Mapper与DAO
MyBatis 实践
标签: Java与存储
MyBatis简介
MyBatis前身是iBatis,是一个基于Java的数据持久层/对象关系映射(ORM)框架.
MyBatis是对JDBC的封装,使开发人员只需关注SQL本身,而不需花费过多的精力去处理如注册驱动、设置参数、创建Connection
/Statement
、解析结果集等JDBC过程性代码.MyBatis基于XML/注解的方式配置Statement
,执行SQL,并将执行结果映射成Java对象, 大大降低了数据库开发的难度.
MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.
– MyBatis项目地址/在线文档.
初识MyBatis
使用MyBatis需要在pom.xml中添加如下依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
Select
- 配置mybatis/mybatis-configuration.xml
作为MyBatis的全局配置文件,其配置了MyBatis的运行环境信息(如数据源/mapper文件等).
<?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>
<environments default="development">
<environment id="development">
<!-- 配置JDBC事务管理-->
<transactionManager type="JDBC"/>
<!-- 配置数据源-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://host:port/db?characterEncoding=utf-8"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<!-- 加载mapper映射文件 -->
<mappers>
<mapper resource="mybatis/mapper/UserDAO.xml"/>
</mappers>
</configuration>
- 书写UserDAO(mapper映射)
最为MyBatis最核心的部分,配置了操作数据库的SQL语句:
<?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="namespace">
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.fq.domain.User">
SELECT * FROM user WHERE id = #{id};
</select>
<select id="selectUserByName" parameterType="java.lang.String" resultType="com.fq.domain.User">
SELECT * FROM user WHERE name LIKE '%${value}%';
</select>
</mapper>
属性 | 描述 |
---|---|
namespace |
命名空间,用于隔离SQL语句 |
parameterType |
定义SQL输入映射类型,MyBatis通过OGNL从输入对象中获取参数传入SQL语句. |
resultType |
定义SQL输出映射类型,MyBatis将SQL查询结果的一行记录映射为resultType 指定的类型. |
mapper映射文件名有UserDAO.xml/UserMapper.xml/User.xml等几种形式, 其一般存放在与mybatis-configuration.xml同级的mapper目录下,由于其主要作用为定义SQL语句与映射关系, 因此一般统称为mapper映射文件.
- 定义PO类
PO类主要作用为SQL(输入/输出)映射,通常与数据库表对应:
/**
* @author jifang
* @since 15/12/31 下午2:27.
*/
public class User {
private Integer id;
private String name;
private String password;
public User() {
}
public User(Integer id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
- UserDAO(Java对象)
获得SqlSession,执行SQL语句, 得到映射结果:
/**
* @author jifang
* @since 16/2/24 下午6:15.
*/
public class UserDAO {
private SqlSessionFactory factory;
@Before
public void setUp() throws IOException {
String resource = "mybatis/mybatis-configuration.xml";
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
}
@Test
public void selectUserById() {
try (SqlSession session = factory.openSession()) {
User user = session.selectOne("namespace.selectUserById", 1);
System.out.println(user);
}
}
@Test
public void selectUserByName() {
try (SqlSession session = factory.openSession()) {
List<User> users = session.selectList("namespace.selectUserByName", "student");
for (User user : users) {
System.out.println(user);
}
}
}
}
Insert
- mapper
<insert id="insertUser" parameterType="com.fq.domain.User">
INSERT INTO user(name, password) VALUES(#{name}, #{password});
</insert>
- UserDAO
@Test
public void insertUser() {
try (SqlSession session = factory.openSession()) {
User user = new User();
user.setName("new_name1");
user.setPassword("new_password");
session.insert("namespace.insertUser", user);
session.commit();
}
}
自增主键返回
修改mapper文件,添加<selectKey/>
,可以将MySQL的自增主键(即刚刚插入数据时生成的ID)返回:
<insert id="insertUser" parameterType="com.fq.domain.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID();
</selectKey>
INSERT INTO user(name, password) VALUES(#{name}, #{password});
</insert>
属性 | 描述 |
---|---|
keyProperty |
指定存储到DO中的哪个属性; |
order |
selectKey 执行顺序(相对于insert 语句),AFTER /BEFORE ; |
resultType |
主键返回类型(DO中对应属性的类型); |
LAST_INSERT_ID() |
MySQL函数,返回auto_increment自增列新记录值. |
- UserDAO
@Test
public void insertUser() {
try (SqlSession session = factory.openSession()) {
System.out.println(session);
User user = new User(null, "new_name", "new_password");
session.insert("namespace.insertUser", user);
// 需要在commit之后才能获得自增主键
session.commit();
System.out.println(user.getId());
}
}
该功能还可以通过
<insert/>
的useGeneratedKeys
/keyProperty
两个属性合作完成, 详见MyBatis文档.
Update
- mapper
<update id="updateUserById" parameterType="com.fq.domain.User">
UPDATE user SET name = #{name}, password = #{password} WHERE id = #{id};
</update>
- UserDAO
@Test
public void updateUserById() {
try (SqlSession session = factory.openSession(true)) {
session.update("namespace.updateUserById",
new User(1, "feiqing", "ICy5YqxZB1uWSwcVLSNLcA=="));
}
}
Delete
- mapper
<delete id="deleteUserById" parameterType="java.lang.Integer">
DELETE FROM user WHERE id = #{id};
</delete>
- UserDAO
@Test
public void deleteUserById() {
try (SqlSession session = factory.openSession(true)) {
session.delete("namespace.deleteUserById", 51615);
}
}
小结
#{}
/${}
#{}
: 表示一个占位符号,实现向PreparedStatement
占位符中设置值(#{}
表示一个占位符?
),自动进行Java类型到JDBC类型的转换(因此#{}
可以有效防止SQL注入).#{}
可以接收简单类型或PO属性值,如果parameterType
传输的是单个简单类型值,#{}
花括号中可以是value
或其它名称.${}
: 表示拼接SQL串,通过${}
可将parameterType
内容拼接在SQL中而不进行JDBC类型转换,${}
可以接收简单类型或PO属性值,如果parameterType
传输的是单个简单类型值,${}
花括号中只能是value
.
虽然${}
不能防止SQL注入,但有时${}
会非常方便(如order by
排序,需要将列名通过参数传入SQL,则用ORDER BY ${column}
,使用#{}
则无法实现此功能(详见JDBC基础关于PreparedStatement
的讨论).
SqlSession
提供操作数据库的方法(如:selectOne
/selectList
).但SqlSession
是线程不安全的,因此最好将其定义成局部变量使用.- MyBatis优点(与JDBC相比)
- SQL写在Java代码中导致不易维护, 而MyBatis将SQL写在mapper中,XML与Java代码分离.
- 向SQL语句传参繁琐(如:SQL的where条件不一,SQL数据类型与Java不同),MyBatis通过
parameterType
自动将Java对象映射至SQL语句. - 结果集解析麻烦(SQL变化导致解析代码变化,SQL数据类型与Java不同),MyBatis通过
resultType
自动将SQL执行结果映射成Java对象.
附: 最好在pom.xml中添加一个日志系统实现(logback/log4j), 这样会在调试程序时打印日志信息,便于查错, 以logback为例:
- pom.xml
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
- logback.xml
<configuration>
<property name="logRoot" value="/data/logs"/>
<property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{0} - %msg%n"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${pattern}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logRoot}/common-server.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${pattern}</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
其他关于MyBatis日志的详细信息可参考MyBatis文档日志部分.
DAO开发
使用MyBatis开发DAO有两个方法,原始DAO开发与Mapper映射DAO开发.
原始DAO开发
原始DAO开发需要开发人员编写DAO接口与DAO实现,如根据ID查询用户信息:
- mapper(同前)
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.fq.domain.User">
SELECT * FROM user WHERE id = #{id};
</select>
- UserDAO接口
/**
* @author jifang
* @since 16/2/22 上午10:20.
*/
public interface UserDAO {
User selectUserById(Integer id) throws Exception;
}
- UserDAO实现
public class UserDAOImpl implements UserDAO {
private SqlSessionFactory factory;
public UserDAOImpl(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public User selectUserById(Integer id) throws Exception {
SqlSession session = factory.openSession();
User user = session.selectOne("namespace.selectUserById", id);
session.close();
return user;
}
}
- Client
public class MyBatisClient {
@Test
public void originalClient() throws Exception {
UserDAO dao = new UserDAOImpl(new SqlSessionFactoryBuilder().
build(ClassLoader.getSystemResourceAsStream("mybatis/mybatis-configuration.xml")));
User user = dao.selectUserById(1);
System.out.println(user);
}
}
- 原始DAO开发中存在的问题:
1) DAO实现方法体中存在很多过程性代码.
2) 调用SqlSession
的方法(select
/insert
/update
)需要指定Statement的id,存在硬编码,不利于代码维护.
Mapper映射开发
mapper映射开发方法只需编写DAO接口,MyBatis根据接口定义与mapper文件中的SQL语句动态创建接口实现.
- mapper
<?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.fq.mybatis.UserDAO">
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.fq.domain.User">
SELECT * FROM user WHERE id = #{id};
</select>
</mapper>
注意: 此时
namespace
必须与UserDAO
接口的全限定名相同.
- UserDAO接口与前面相同, 但不再使用
UserDAOImpl
- Client
/**
* @author jifang
* @since 16/2/22 下午2:57.
*/
public class MyBatisClient {
private SqlSession session;
private SqlSessionFactory factory;
@Before
public void setUp() {
factory = new SqlSessionFactoryBuilder().
build(ClassLoader.getSystemResourceAsStream("mybatis/mybatis-configuration.xml"));
session = factory.openSession();
}
@Test
public void mapperClient() throws Exception {
UserDAO dao = session.getMapper(UserDAO.class);
User user = dao.selectUserById(1);
System.out.println(user);
}
@After
public void tearDown() {
session.close();
}
}
- mapper映射开发方法需要遵循以下规范:
- mapper文件中的namespace与DAO接口的全限定名相同;
- mapper文件中的Statement的id与DAO接口方法名相同;
- mapper文件中的Statement的
parameterType
/resultType
与DAO方法的入参/回参类型相同.
Mapper映射
mapper映射文件(如UserDAO.xml)主要作用是定义SQL语句(每个SQL是一个Statement),是MyBatis的核心.
MyBatis官方推荐使用mapper映射的方法来开发DAO,因此我们以后就不再过多介绍原始DAO的开发.
输入映射
多个形参
传递简单类型前面示例已经使用过,在此就不再赘述.当需要传递多个形参时,不再需要设置parameterType
参数:
- mapper
<update id="updateUserById">
UPDATE user SET name = #{1}, password = #{2} WHERE id = #{0};
</update>
- UserDAO
void updateUserById(Integer id, String name, String password) throws Exception;
传入PO
MyBatis使用OGNL表达式解析对象属性值:
- mapper
<select id="selectUserByNamePassword" parameterType="com.fq.domain.User" resultType="com.fq.domain.User">
SELECT *
FROM user
WHERE name = #{name} AND password = #{password};
</select>
- UserDAO
User selectUserByNamePassword(User user) throws Exception;
传入Map
- mapper
<select id="selectUserByMap" parameterType="java.util.Map" resultType="com.fq.domain.User">
SELECT *
FROM user
WHERE name = #{name} AND password = #{password};
</select>
#{}
花括号内对应Map
的key
.
- UserDAO
User selectUserByMap(Map<String, Object> map) throws Exception;
输出映射
输出简单类型
- mapper
<select id="selectUserCount" parameterType="java.lang.String" resultType="java.lang.Integer">
SELECT count(*)
FROM user
WHERE name LIKE '%${value}%';
</select>
- UserDAO
Integer selectUserCount(String name) throws Exception;
返回简单类型必须保证查询结果只有一行记录,最终将第一个字段的值转换为输出类型.
输出PO对象/列表
- 前面已经演示过输出两种类型(
selectUserById
/selectUserByName
虽然当时使用的是原始DAO开发方法, 但mapper定义形式大同小异),因此在这儿只做简单总结:- 输出单个PO对象和输出PO列表在mapper中定义的
resultType
是一样的; - 输出单个PO对象要保证SQL查询结果为单条数据,其内部使用
selectOne
方法调用; - 输出PO列表表示查询结果可能为多条,其内部使用
selectList
方法调用,接口返回值可用List<PO>
/Set<PO>
承载.
- 输出单个PO对象和输出PO列表在mapper中定义的
输出Map
输出PO对象完全可以改用Map
输出,字段名作key,字段值作value.
- mapper
<select id="selectUserLikeName" resultType="java.util.Map">
SELECT *
FROM user
WHERE name LIKE '%${value}%';
</select>
- UserDAO
List<Map<String, Object>> selectUserLikeName(String name) throws Exception;
resultMap
resultType
可将查询结果映射为PO,但前提是PO属性名与SQL字段名必须一致,如不一致,则可通过resultMap
作对应映射:
- mapper
<resultMap id="userMap" type="com.fq.domain.User">
<id column="user_id" property="id"/>
<result column="user_name" property="name"/>
<result column="user_password" property="password"/>
</resultMap>
<select id="selectUserByName" parameterType="java.lang.String" resultMap="userMap">
SELECT
id user_id,
name user_name,
password user_password
FROM user
WHERE name = #{name};
</select>
属性 | 描述 |
---|---|
<id/> |
表示查询结果集的唯一标识; |
<result/> |
表示普通结果,即PO属性; |
column |
表示SQL查询出来的字段名, |
property |
表示PO属性. |
- UserDAO接口同前.
MyBatis 实践 -Mapper与DAO的更多相关文章
- 使用MyBatis Generator自动生成实体、mapper和dao层
原文链接 通过MyBatis Generator可以自动生成实体.mapper和dao层,记录一下怎么用的. 主要步骤: 关于mybatis从数据库反向生成实体.DAO.mapper: 参考文章:ht ...
- MyBatis 实践 -动态SQL/关联查询
MyBatis 实践 标签: Java与存储 动态SQL 动态SQL提供了对SQL语句的灵活操作,通过表达式进行判断,对SQL进行拼接/组装. if 对查询条件进行判断,如果输入参数不为空才进行查询条 ...
- MyBatis 实践 -配置
MyBatis 实践 标签: Java与存储 Configuration mybatis-configuration.xml是MyBatis的全局配置文件(文件名任意),其配置内容和顺序如下: pro ...
- Spring+MyBatis实践—MyBatis数据库访问
关于spring整合mybatis的工程配置,已经在Spring+MyBatis实践—工程配置中全部详细列出.在此,记录一下几种通过MyBatis访问数据库的方式. 通过sqlSessionTempl ...
- Spring+MyBatis实践—工程配置
初次实践:Spring+MyBatis技术搭建框架,采用Bootstrap前端开源框架. 简介: MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所 ...
- springboot学习笔记:8. springboot+druid+mysql+mybatis+通用mapper+pagehelper+mybatis-generator+freemarker+layui
前言: 开发环境:IDEA+jdk1.8+windows10 目标:使用springboot整合druid数据源+mysql+mybatis+通用mapper插件+pagehelper插件+mybat ...
- Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)
整体目录结构: 其中包下全部是采用mybatis自动生成工具生成. mybatis自动生成文件 <?xml version="1.0" encoding="UTF- ...
- 关于mybatis 的mapper namespace 作用及解析
因为语言惯性,大部分的namespace 在语言级别*来说是作为一种限定性标识来用,起到唯一或一类的标识.来看看语言(以PHP语言为例)上的namespace的作用实例 一.namespace 在PH ...
- Spring Boot MyBatis 通用Mapper插件集成
Mybatis在使用过程中需要三个东西,每张表对应一个XXMapper.java接口文件,每张表对应一个XXMapper.xml文件,每张表对应一个Entity的Java文件. 其中XXMappe ...
随机推荐
- 登录超时自动退出,计算时间差-b
// 此方法适用于所有被创建过的controller,且当前controller生命周期存在,如有错误的地方望大神斧正 // 说一下我们的需求和实现原理,需求:在点击home键退出但没有滑飞它,5分 ...
- Module模式 - 深入了解Javascript
/* Modelu模式 优点:效率高,代码少,加载速度快,松耦合允许并行加载,提升下载速度 缺点:初始化时间久一点 */ //一.基础用法 var calculate = function (eq) ...
- 一个inline-block的样式。
<span style="height: 31px; font-family: 宋体; font-weight: bold; color: rgb(160, 64, 0); text- ...
- 移动端页面使用rem来做适配
文/九彩拼盘(简书作者)原文链接:http://www.jianshu.com/p/eb05c775d3c6著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. rem介绍 rem(font ...
- MSAA
多重采样抗锯齿(MultiSampling Anti-Aliasing,簡稱MSAA)是一种特殊的超级采样抗锯齿(SSAA).MSAA首先来自于OpenGL.具体是MSAA只对Z缓存(Z-Buffer ...
- MATLAB——axis
MATLAB——axis axis中文为“轴”之意,在matlab中用于控制坐标轴的范围和样式(颜色等). axis([XMIN XMAX YMIN YMAX]) 设置当前所绘图像的x轴和y轴的范围. ...
- POJ1013Counterfeit Dollar
这个题主要是判断硬币真假,可能轻可能重,称三次,要输出哪枚是假币,还要输出是重的还是轻的,所以最主要的是标记变量 #include<cstdio> #include<cstring& ...
- Android 调节当前Activity的屏幕亮度
调节的关键代码: WindowManager.LayoutParams layoutParams = getWindow().getAttributes(); layoutParams.screenB ...
- nginx/apache/php隐藏http头部版本信息的实现方法
有时候我们需要隐藏我们的服务器版本信息,防止有心人士的研究,更安全,这里介绍下在nginx/apache/php中如何隐藏http头部版本信息的方法. nginx隐藏头部版本信息方法 编辑nginx. ...
- [OOD]违反里氏替换原则的解决方案
关于OOD中的里氏替换原则,大家耳熟能祥了,不再展开,可以参考设计模式的六大设计原则之里氏替换原则.这里尝试讨论常常违反的两种形式和解决方案. 违反里氏替换原则的根源是对子类及父类关系不明确.我们在设 ...