三、 增删改查 CRUD
注意:增删改需要提交事务
1. namespace
将入门程序中的 UserDao 接口改为 UserMapper 接口,需要将 UserMapper.xml 中的namespace 改为 UserMapper 的路径,在 Mybatis 核心配置文件中注册信息也要修改。
结论:配置文件中 namespace 中的名称为对应 Mapper/Dao 接口的完整包名,必须一致!
2. select 查询
- id:对应 namespace 中的方法名
- resultType:Sql 语句执行的返回值【完整的类名或者别名】
- parameterType:传入 SQL 语句的参数类型【可以是基本类型、对象、以及万能的 Map】
-
编写接口中的方法
public interface UserMapper { // 根据 id 查询用户 User getUserById(int id); }
-
编写对应的 Mapper.xml 中的 SQL 语句
<mapper namespace="com.song.dao.UserMapper"> <select id="getUserById" parameterType="int" resultType="com.song.pojo.User"> select * from mybatis.user where id = #{id} </select> </mapper>
-
测试
@Test public void getUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); // 获得接口的实现类对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); sqlSession.close(); }
3. insert 增加
// 插入一个用户
int addUser(User user);
<!-- 对象中的属性可以直接取出来 -->
<insert id="addUser" parameterType="com.song.pojo.User" >
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
</insert>
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(4, "赵六", "123456"));
// 提交事务
sqlSession.commit();
sqlSession.close();
}
4. update 修改
// 修改用户
int updateUser(User user);
<update id="updateUser" parameterType="com.song.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(4, "哈哈", "111111"));
sqlSession.commit();
sqlSession.close();
}
5. delete 删除
// 删除一个用户
int deleteUser(int id);
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(4);
sqlSession.commit();
sqlSession.close();
}
小结:
- 标签不要匹配错
- Mybatis 核心配置文件中注册 Mapper.xml 文件时,resource 中的路径一定要以斜杠(/)分隔!
- 程序配置文件必须符合规范
- sqlSessionFactory 的作用域要注意,写错的话有可能空指针异常,没有注册到资源
- Mapper.xml 文件中的 SQL 语句中用了 mybatis.user,是因为 IDEA 连接了数据库,如果 IDEA 没连接数据库,直接写表名即可
6. 传递参数的方法(注解直接传递和万能 Map)
如果实体类或者数据库中的表,字段或参数过多,应当考虑使用map;如果参数比较少,直接传递参数即可。
(1)直接在方法中传递参数
- 在接口方法的参数前加 @Param属性
User getUserByNP(@Param("username")String username, @Param("pwd")String pwd);
- Sql语句编写的时候,直接取@Param中设置的值即可,不需要单独设置参数类型
<select id="getUserByNP" resultType="com.song.pojo.User">
select * from mybatis.user where name = #{username} and pwd = #{pwd}
</select>
@Test
public void getUserByNP() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 获得接口的实现类对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByNP("张三","123456");
System.out.println(user);
sqlSession.close();
}
(2)万能Map
- 在接口方法中,参数直接传递Map;
// 万能的 Map
int addUser2(Map<String, Object> map);
- 编写sql语句的时候,需要传递参数类型,参数类型为map
<!-- 传递 map 中的 key -->
<insert id="addUser2" parameterType="map" >
insert into mybatis.user (id, name, pwd) values (#{userid}, #{username}, #{password});
</insert>
- 在使用方法的时候,Map的 key 为 SQL 中设置的值,且没有顺序要求!
@Test
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userid", 5);
map.put("username","呵呵");
map.put("password","123456");
mapper.addUser2(map);
// 提交事务
sqlSession.commit();
sqlSession.close();
}
-
map 传递参数,直接在 SQL 中取出 key 即可!【parameterType="map"】
-
对象 传递参数,直接在 SQL 中取出对象的属性即可!【parameterType="Object"】
-
只有一个基本类型参数的情况下,可以直接在 SQL 中取到!【parameterType="int" 也可以省略不写】
7. 模糊查询(两种方式)
- 第一种:Java 代码执行时,传递通配符 % %
// 模糊查询
List<User> getUserLike(String value);
<select id="getUserLike" resultType="com.song.pojo.User">
select * from mybatis.user where name like #{value}
</select>
List<User> userList = mapper.getUserLike("%李%");
- 第二种:在 SQL 拼接中使用通配符,但是会引起 SQL 注入
<select id="getUserLike" resultType="com.song.pojo.User">
select * from mybatis.user where name like "%"#{value}"%"
</select>
List<User> userList = mapper.getUserLike("李");
注意:SQL 注入问题:
select * from mybatis.user where id = ?
select * from mybatis.user where id = 1 or 1=1 ==> sql注入问题
四、配置解析
1. 核心配置文件
- mybatis-config.xml 文件是系统核心配置文件
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
- 能配置的内容如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<!-- 注意元素节点的顺序!顺序不对会报错 -->
注意标签的位置:在 xml 中,每个标签都规定了其顺序
2. 环境配置(environments)
MyBatis 可以配置成适应多种环境,将SQL映射到多个不同的数据库上,通过设置id进行区别,id保证唯一!必须指定其中一个为默认运行环境(通过 default 指定某个 id 的环境)。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
数据源 dataSources
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。是必须配置的,一般都使用 POOLED。
有三种内建的数据源类型:type="[UNPOOLED|POOLED|JNDI]"
- unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。
- pooled:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。池子:用完可以回收
- jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
- 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等....
事务管理器 (transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"),这两种事务管理器类型都不需要设置任何属性。
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。【默认使用】
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。【一般不使用】
3. 属性(properties)
可以通过 properties 属性来实现引用配置文件。
这些属性可以在外部进行配置,并可以进行动态替换。既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.properties】
1) 在资源目录下新建一个配置文件 db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=root
2) 在核心配置文件 mybatis-config.xml 中引入 db.properties
<!--引入外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
- 可以直接引入外部配置文件
- 可以在其中增加一些属性配置
- 如果两个文件中有同一个字段,优先使用外部配置文件的!
4. 类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 存在的意义在于降低冗余的全限定类名书写。
两种方式实现:
第一种,直接起别名,当这样配置时,User
可以用在任何使用com.song.pojo.User
的地方。
<!--给实体类起别名-->
<typeAliases>
<typeAlias type="com.song.pojo.User" alias="User"/>
</typeAliases>
第二种,也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean。
扫描实体类的包,它的默认名就为这个类的 类名及首字母小写,首字母大写也是可以的,官方建议小写。
每一个在包
domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如domain.blog.Author
的别名为author
;若有注解,则别名为其注解值。
<!--给实体类起别名-->
<typeAliases>
<package name="com.song.pojo"/>
</typeAliases>
两种方式比较:
-
实体类比较少时,使用第一种;
-
如果实体类十分多,建议使用第二种;
-
第一种可以 DIY 别名;第二种不行,如果非要改,需要在实体类上增加注解
@Alias("user")
public class User {
.....
}
官方为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。例如:Map 别名 map;Integer 别名 int;int 别名 _int / _integer。
5. 设置(settings)
MyBatis 中极为重要的设置,它们会改变 MyBatis 的运行时行为。
- 日志实现
- 缓存开启关闭
- 懒加载
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING| STDOUT_LOGGING | NO_LOGGING | 未设置 |
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true | false | false |
类型处理器 mapUnderscoreToCamelCase
- 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
- 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。【了解即可】
6. 其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core
- mybatis-plus
- 通用 mapper
7. 映射器 (mappers)
映射器 : 定义映射SQL语句文件
MapperRegister:注册绑定 Mapper 文件
引入资源(注册绑定 Mapper 文件)方式:
方式一:使用相对于类路径的资源引用【推荐使用】
<!-- 每一个Mapper.XML 都需要在 Mybatis 核心配置文件中注册!!-->
<mappers>
<mapper resource="com/song/dao/UserMapper.xml"/>
</mappers>
方式二:使用 class 文件(映射器接口实现类的完全限定类名)绑定注册
<mappers>
<mapper class="com.song.dao.UserMapper"/>
</mappers>
注意点:
- 接口和它的 Mapper 配置文件必须同名
- 接口和它的 Mapper 配置文件必须在同一个包/目录下
方式三:使用扫描包进行注入绑定(将包内的映射器接口实现全部注册为映射器)
【和方式二的注意点一样】
<mappers>
<package name="com.song.dao"/>
</mappers>
注意:一定要让接口和它的 xml 配置文件同名,并且放在同一个包下;如果想要实现分离,不放在同一个包下,可以在 resources 下创建和接口相同路径的文件夹(文件夹的一级一级的建),把 xml 文件放进去。
方式四:使用完全限定资源定位符(URL)【不使用】
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
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">
<!--namespace = 绑定一个对应的 Dao/Mapper 接口 命名规则:包名+类名-->
<mapper namespace="com.song.dao.UserMapper">
</mapper>
MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。
8. 生命周期和作用域
作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
-
作用:加载 MyBatis 核心配置文件生成 SqlSessionFactory 对象
-
一旦创建了 SqlSessionFactory,就不再需要它了,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。
-
局部变量
SqlSessionFactory:
- 可以被认为是一个:数据库连接池
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,因为 MyBatis 的本质就是 Java 对数据库的操作
- 因此 SqlSessionFactory 的最佳作用域是应用作用域,生命周期就等同于 MyBatis 的应用周期。
- 最简单的就是使用单例模式或者静态单例模式。因为:如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
SqlSession:
- 连接到连接池的一个请求,即相当于一个数据库连接(Connection 对象)
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 应该存活在一个业务请求中,处理完整个请求后,赶紧关闭这条连接,让它归还给 SqlSessionFactory,否则资源被占用
映射器实例 Mapper:
通过 sqlSession.getMapper 可以获取 Mapper 对象,每一个 Mapper 代表请求中一个具体的业务步骤,随着 SqlSession 的关闭而废弃它。
五、解决属性名和字段名不一致的问题
1. 问题分析
问题:
- 数据库中的字段:
- Java 中实体类中的字段(属性名 password 与数据库字段名 pwd 不一致):
public class User {
private int id;
private String name;
private String password; //和数据库中的字段不一样
//构造
//set/get
//toString()
}
- 接口
// 根据 id 查询用户
User getUserById(int id);
- mapper映射文件
<select id="getUserById" resultType="User">
select * from mybatis.user where id = #{id}
</select>
- 测试
@Test
public void getUserById() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
结果:
-
User{id=1, name='张三', password='null'}
-
查询出来发现 password 为空,说明出现了问题!
分析:
select * from mybatis.user where id = #{id} 可以看做
// 类型处理器
select id,name,pwd from mybatis.user where id = #{id}
MyBatis 会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的 set 方法设值,由于找不到 setPwd(),所以 password 返回 null 。【自动映射】
解决方法:
- 方案一:为数据库列名起别名,别名和java实体类的属性名一致
<select id="getUserById" parameterType="int" resultType="com.song.pojo.User">
select id,name,pwd as password from mybatis.user where id = #{id}
</select>
- 方案二:使用结果集映射 ——resultMap
数据库中字段:id name pwd
实体类中属性:id name password
<!--结果集映射,id 和下面 select 标签中 resultMap 的值对应,type 为映射成的类型-->
<resultMap id="UserMap" type="User">
<!-- column 数据库中的字段, property 实体类中的属性-->
<!-- <result column="id" property="id"/> -->
<!-- <result column="name" property="name"/> -->
<result column="pwd" property="password"/>
</resultMap>
<!--引用 resultMap 属性,其中 UserMap 是自己起的名字,不用 resultType 属性-->
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
2. resultMap(结果集映射)
-
resultMap 元素是 MyBatis 中最重要最强大的元素。
-
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
-
ResultMap 的优秀之处在于,虽然你已经对它相当了解,但是根本就不需要显式地用到它们。简明来说就是,哪个字段不一样转哪个,相同的不用转,例如上面的 id 和 name 不需要转,只转 password 为 pwd 即可。
-
在引用 ResultMap 的语句中设置 resultMap 属性就行了(注意去掉了 resultType 属性)
自动映射
简单映射语句的示例了,但并没有显式指定 resultMap
。比如:
<select id="getUserById" resultType="User">
select id,name,pwd from mybatis.user where id = #{id}
</select>
简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 不是一个很好的模型。因为程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。
手动映射(例子见方案二)
- 返回值类型为 resultMap
- 编写 resultMap,实现手动映射
数据库中,存在一对多,多对一的情况,之后会使用到一些高级的结果集映射,如association,collection等。