MyBatis源码及资料: https://github.com/coderZYGui/MyBatis-Study
MyBatis系列
- MyBatis — ORM思想、MyBatis概述、日志框架、OGNL
- MyBaits — MyBatis的CRUD操作、别名配置、属性配置、查询结果映射、Mapper组件、参数处理、注解开发
- MyBatis — 动态SQL、if、where、set、foreach、sql片段
- MyBatis — 对象关系映射、延迟加载、关联对象的配置选择
- MyBatis — 缓存机制、EhCache第三方缓存
- MyBatis — MyBatis Generator插件使用(配置详解)
跳转到目录
public interface UserMapper {
// 查询所有用户
List<User> getUserList();
// 根据id来查询用户
User getUser(int id);
// 根据id来更新用户信息
int updateUser(User user);
// 插入一条用户信息
int insertUser(User user);
// 根据id来删除用户
int deleteUser(int id);
}
select元素: 专门用来做查询的SQL
-id属性: 唯一标识,用来标识某一条SQL语句
-parameterType属性: 表示执行该SQL语句需要的参数的类型(建议不写),MyBatis可以自行推断出来
-resultType属性: 把结果集中每一行数据封装成什么类型的对象
自动增长
的主键insert元素:
useGeneratedKeys
属性: 是否需要返回自动生成的主键
keyProperty
: 把自动生成的主键值设置到对象的哪一个属性上
非自动增长
的主键
selectKey
中resultType属性指定期望主键的返回的数据类型,
keyProperty
属性指定实体类对象接收该主键的字段名
order
属性指定执行查询主键值SQL语句是在插入语句执行之前还是之后(可取值:after和before)
<insert id="insertDataAgain" useGeneratedKeys="true" keyProperty="id">
<selectKey resultType="integer" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
selectKey>
insert into tableName values(null,#{name})
insert>
<mapper namespace="com.sunny.dao.UserMapper">
<select id="getUserList" resultType="com.sunny.domain.User">
SELECT * FROM user;
select>
<select id="getUser" parameterType="int" resultType="com.sunny.domain.User">
SELECT * FROM user WHERE id = #{id};
select>
<update id="updateUser" parameterType="com.sunny.domain.User">
UPDATE user SET name = #{name}, pwd = #{pwd} WHERE id = #{id};
update>
<insert id="insertUser" parameterType="com.sunny.domain.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (id, name, pwd) VALUES (null , #{name}, #{pwd});
insert>
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE id = #{id};
delete>
mapper>
public class UserMapperTest {
/**
* 自定义日志操作
*/
private static Logger logger = Logger.getLogger(UserMapperTest.class);
@Test
public void testLogger() throws Exception {
// 如果日志输出级别为INFO,则输出
if (logger.isInfoEnabled()) {
logger.info("银行转账操作");
}
if (logger.isDebugEnabled()) {
logger.debug("查询数据库");
}
if (logger.isTraceEnabled()) {
logger.trace("连接数据库");
}
if (logger.isTraceEnabled()) {
logger.trace("执行SQL");
}
if (logger.isDebugEnabled()) {
logger.debug("转账");
}
if (logger.isInfoEnabled()) {
logger.info("银行转账成功");
}
}
/**
* 查询所有用户
*
* @throws IOException
*/
@Test
public void testQueryUserList() throws IOException {
//1. 获得sqlSession对象
// SqlSession sqlSession = MybatisUtils.getSqlSession();
//1. 从classpath路径去加载MyBatis全局配置文件:mybatis-config.xml
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2. 创建sqlSessionFactory对象,好比是DataSource
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 创建sqlSession对象,好比是Connection
SqlSession sqlSession = factory.openSession();
//4. 具体操作
// 执行SQL(方式一)
/*
执行SQL: UserMapper.xml中的namespace:绑定一个对应的Dao/Mapper接口相当于UserMapper接口的实现类,
这里用UserMapper.class,就是面向接口编程,相当于获取UserMapper的实现类,然后通过实现类来调用接口方法
*/
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
// 方式二
// List userList = sqlSession.selectList("com.sunny.dao.UserDao.getUserList");
for (User user : userList) {
System.out.println(user);
}
// 关闭sqlSession
sqlSession.close();
}
/**
* 查询id为1的用户
*
* @throws IOException
*/
@Test
public void testQueryOneUser() throws IOException {
// 加载全局配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 构建sqlSessionFactory工厂类对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 根据sqlSession类对象来创建SqlSession对象
SqlSession sqlSession = factory.openSession();
// sqlSession相当于Connection,来执行SQL语句
//User user = sqlSession.selectOne("com.sunny.dao.UserMapper.getUser", 1L);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUser(1);
System.out.println(user);
sqlSession.close();
}
/**
* 修改一条语句的内容
*
* @throws Exception
*/
@Test
public void testUpdateUser() throws Exception {
User user = new User();
user.setId(4);
user.setName("土土土雅");
user.setPwd("10004");
// 获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 方式一
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int update = mapper.updateUser(user);
// 方式二(以前的,不推荐写)
// int update = sqlSession.update("com.sunny.dao.UserMapper.update",user);
if (update > 0) {
System.out.println("成功修改了:" + update + " 条语句!");
}
// 增删改必须提交事务
sqlSession.commit();
// 关闭资源
sqlSession.close();
}
/**
* 插入一条数据
* @throws Exception
*/
@Test
public void testInsertUser() throws Exception {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
/*
执行SQL: UserMapper.xml中的namespace:绑定一个对应的Dao/Mapper接口相当于UserMapper接口的实现类,
这里用UserMapper.class,就是面向接口编程,相当于获取UserMapper的实现类,然后通过实现类来调用接口方法
*/
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User u = new User("coder", "10007");
int count = mapper.insertUser(u);
if (count > 0){
System.out.println("插入成功!");
}
// 提交事务
sqlSession.commit();
// 关闭资源
sqlSession.close();
System.out.println(u);
}
/**
* 根据id来删除用户
* @throws Exception
*/
@Test
public void testDeleteUser() throws Exception{
// 加载mybatis全局配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 构建SqlSessionFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 根据工厂对象来创建SqlSession对象
// 这样的话,下面就不用手动提交事务了,设置了自动提交事务
SqlSession sqlSession = factory.openSession(true);
// SqlSession sqlSession = factory.openSession();
// 根据sqlSession对象来获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 通过mapper来调用接口方法
int count = mapper.deleteUser(5);
if (count > 0){
System.out.println("删除成功");
}
// 提交事务
// sqlSession.commit();
// 关闭资源
sqlSession.close();
}
}
注意: 增删改操作,必须要手动提交事务,否则对该操作不起作用!
SqlSession sqlSession = factory.openSession(true);, 就不用手动提交事务了;
跳转到目录
跳转到目录
<select id="getUserList" resultType="com.sunny.domain.User">
SELECT * FROM user;
select>
<select id="getUser" resultType="com.sunny.domain.User">
SELECT * FROM user WHERE id = #{id};
select>
注意: 当写resultType结果类型时,都使用全限定类名com.sunny.domain.User
,很麻烦,可以使用类型别名
使用别名(typeAlias)是一个类全限定名太长,不方便.MyBatis中别名不区分大小写
跳转到目录
<typeAlias type="com.sunny.domain.User" alias="User" />
<package name="com.sunny.domain"/>
一般写到domain包就可以了,自动为该包中的类名起别名,默认的别名就是类名首字母小写(不区分大小写)
<typeAliases>
<package name="com.sunny.domain"/>
typeAliases>
前两种设置别名的使用:
在实体类比较少的时候,使用第一种方式。
如果实体类十分多,建议使用第二种方式。
Mapper.xml中的查询操作(使用别名)
<select id="getUserList" resultType="com.sunny.domain.User">
SELECT * FROM user;
select>
<select id="getUser" resultType="com.sunny.domain.User">
SELECT * FROM user WHERE id = #{id};
select>
注意: 在全局配置文件中<configuration>
标签中属性设置,日志设置,别名设置.都有严格的顺序,不能打乱.
eg:
public interface UserMapper {
// 只查询User表的id和name列
List<User> queryUserByIdAndName();
List<Map<Integer, Object>> queryUserByIdAndName2();
}
public class UserMapperTest {
/**
* 只查询User表中的id和name列
* @throws Exception
*/
@Test
public void testQueryUserByIdAndName(){
/*
这种方式来查询User表中的部分列,其他列会显示null,可以将
*/
/*SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List users = mapper.queryUserByIdAndName();
for (User user : users) {
System.out.println(user);
}*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Map<Integer, Object>> maps = mapper.queryUserByIdAndName2();
for (Map<Integer, Object> map : maps) {
System.out.println(map);
}
}
}
<mapper namespace="com.sunny.dao.UserMapper">
<select id="queryUserByIdAndName2" resultType="map">
SELECT id, name FROM user;
select>
mapper>
一般用于接口方法返回值类型, 当接口方法返回为为int时, 可以在Mapper.xml文件中ResultType设置为int(默认), _int(别名)
,对大小写不敏感.
跳转到目录
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="1111"/>
properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
db.properties
文件:driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=1111
<properties resource="db.properties"/>
跳转到目录
resultType="User"
来设置MyBatis把结果集中的每一行数据包装为User对象,此时必须要求数据表中的列名和对象的属性名一一对应.跳转到目录
<select id="queryUserList" resultMap="BaseResultMap">
SELECT u_id AS id, u_name AS name, u_pwd AS pwd FROM user1;
select>
跳转到目录
resultMap元素: 结果集对象的映射
id属性: 当前Mapper文件中resultMap的唯一名称
type属性: 把结果集中的每一行数据封装成什么类型的对象
子元素:
id元素: 功能和result一样,如果是主键,建议使用id元素,提升性能
result元素: 匹配对象中的哪一个属性对应表中的哪一个列
<mapper namespace="com.sunny.dao.UserMapper">
<select id="queryUserCount" resultType="int">
SELECT COUNT(id) FROM user1;
select>
<resultMap id="BaseResultMap" type="User">
<result column="u_id" property="id" />
<result column="u_name" property="name" />
<result column="u_pwd" property="pwd" />
resultMap>
<select id="queryUserList" resultMap="BaseResultMap">
select u_id, u_name, u_pwd from user1;
select>
mapper>
跳转到目录
Mapper组件: Mapper接口 + Mapper文件
下面有两种方式通过sqlSession
来获取SQL并执行,下面说说两种方式的优缺点:
/**
* 查询id为1的用户
*
* @throws IOException
*/
@Test
public void testQueryOneUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 使用方式一: 来找到SQL并执行
//User user = sqlSession.selectOne("com.sunny.dao.UserMapper.getUser", 1L);
// 使用方式二: 来找到SQL并执行
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 实际上底层调用的还是sqlSession的方法,注意:sqlSession调用CRUD方法只能传递一个参数
User user = mapper.getUser(1);
System.out.println(user);
sqlSession.close();
}
namespace.id
(方式一)的方式去找到SQL元素的方式,会有几个问题:这种方式相对于方式二,我觉得唯一的优点就是在接口中不需要定义接口方法,直接和映射文件关联起来了.
解决方案: 使用Mapper接口,类似DAO接口,在Mapper接口中定义每一个操作方法.(上面已经使用过,这里总结一下)
跳转到目录
使用Mapper组件:
创建一个Mapper接口(类似于DAO接口) , 接口的要求:
①这个接口的全限定名称对应着 Mapper文件的namespace ;
②这个接口中的方法和Mapper文件中的SQL元素一一对应 ;
方法的名字对应SQL元素的id ;
方法的参数类型对应SQL元素中定义的paramterType类型( -般不写) ;
方法的返回类型对应SQL元素中定义的resultType/resultMap类型;
创建SqlSession ;
通过SqlSession.getMapper(XxxMapper.class)方法得到一个Mapper对象;
调用Mapper对象上的方法完成对象的CURD ;
跳转到目录
注意: 在SqlSession中的CRUD方法最多只能传递一个惨. 即使上面的方式二,底层仍是使用SqlSession来调用CRUD方法,如何来解决CRUD方法中只能传递一个参数呢?
方式一: 将参数封装到一个JavaBean中,将JavaBean对象作为参数传递
方式二: 使用Map来封装参数_键值对的方式
方式三: 使用注解 @Param
Param注解中的字符串就是Map中的key
public interface ClientMapper {
// 方式一: 把多个参数封装成JavaBean
Client login1(LoginVO vo);
// 方式二: 使用Map对象封装多个参数
Client login2(Map<String, Object> paramMap);
// 方式三: 使用Param注解, 原理是方式二. Param注解中的字符串就是Map中的key
Client login3(@Param("username") String username, @Param("password") String password);
}
<mapper namespace="com.sunny.dao.ClientMapper">
<select id="login1" parameterType="LoginVo" resultType="Client">
SELECT id, username, password FROM client WHERE username = #{username} AND password = #{password};
select>
<select id="login2" parameterType="map" resultType="Client">
SELECT id, username, password FROM client WHERE username = #{username} AND password = #{password};
select>
<select id="login3" resultType="Client">
SELECT id, username, password FROM client WHERE username = #{username} AND password = #{password};
select>
mapper>
public class ClientMapperTest {
/**
* 方式一: 使用JavaBean来封装
*/
@Test
public void testLogin1(){
LoginVO vo = new LoginVO("zy", "1111");
SqlSession sqlSession = MybatisUtils.getSqlSession();
ClientMapper mapper = sqlSession.getMapper(ClientMapper.class);
// mapper调用方法,实际底层仍是sqlSession来调用select方法
Client client = mapper.login1(vo);
System.out.println(client);
sqlSession.close();
}
/**
* 使用Map来封装
*/
@Test
public void testLogin2(){
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("username", "zy");
paramMap.put("password", "1111");
SqlSession sqlSession = MybatisUtils.getSqlSession();
ClientMapper mapper = sqlSession.getMapper(ClientMapper.class);
// mapper调用方法,实际底层仍是sqlSession来调用select方法.
// 注意: sqlSession调用的方法,只能传递一个参数.
Client client = mapper.login2(paramMap);
System.out.println(client);
sqlSession.close();
}
/**
* 方式三: 使用Param注解,原理是方式二
*/
/*
HelloWorld
*/
@Test
public void testLogin3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
ClientMapper mapper = sqlSession.getMapper(ClientMapper.class);
// mapper调用方法,实际底层仍是sqlSession来调用select方法.
// 注意: sqlSession调用的方法,只能传递一个参数.
Client client = mapper.login3("zy", "1111");
System.out.println(client);
sqlSession.close();
}
}
跳转到目录
使用 #
Preparing: SELECT id, username, password FROM client WHERE username = ? AND password = ?;
Parameters: zy(String), 1111(String)
使用 $
Preparing: SELECT id, username, password FROM client WHERE username = root AND password = 1111;
Parameters:
一、两者的异同
相同: 都可以通过 # 和 $ 来获取对象中的信息
不同:
1. 使用#传递的参数会先转换为占位符?, 通过设置占位符参数的方式来设置值(会给值使用单引号引起来)
2. 使用$传递的参数,直接把解析出来的数据作为SQL语句的一部分
二、推论
#: 好比使用PrepareStatement,不会导致SQL注入问题,相对安全
$: 好比使用Statement, 可能会导致SQL注入问题,相对不安全.
三、如何选择
跳转到目录
注意: 使用注解来配置的话, 就不需要Mapper.xml文件了,在主配置文件中注册映射文件的方式就要改为:
或者
该包下的mapper都注册
在接口方法上添加注解
// 插入一条用户信息
@Insert("INSERT INTO user (id, name, pwd) VALUES (null , #{name}, #{pwd})")
@Options(useGeneratedKeys = true, keyProperty = "id") //生成主键
int insertUser(User user);
// 根据id来删除用户
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(int id);
// 根据id来更新用户信息
@Update("UPDATE user SET name = #{name}, pwd = #{pwd} WHERE id = #{id}")
int updateUser(User user);
// 查询某个用户
@Select("SELECT id AS u_id, name AS u_name, pwd AS u_pwd FROM user WHERE id = ${id}")
@ResultMap("BaseResultMap")
User getOneUser(int id);
// 查询所有用户
@Select("SELECT id AS u_id, name AS u_name, pwd AS u_pwd FROM user")
@Results(id = "BaseResultMap", value = {
@Result(column = "u_id", property = "id"),
@Result(column = "u_name", property = "name"),
@Result(column = "u_pwd", property = "pwd")
})
List<User> getUserList();