1.mybatis入门
1.环境配置:
要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
2.mybatis核心配置文件:
<!--常命名为mybatis-config.xml-->
<?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">
<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>
<!--映射到 Mapper.xml-->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。
从 XML 中构建 SqlSessionFactory:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
3.编写mapper.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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
4.常见异常:
很有可能是maven的问题 由于maven的约定大于配置,我们可能遇到所写的配置文件无法导出或生效
-
java.lang.ExceptionInInitializerError 初始化异常
资源过滤问题:没有加载到target资源目录中
解决方法:
在build中配置resources,来防止我们资源导出失败的问题 <!-- 在build中配置resources , 来防止我们资源导出失败的问题--> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> </resources> </build>
-
org.apache.ibatis.binding.BindingException: Type interface com.song.dao.UserDao is not known to the MapperRegistry. mapper 没有注册
每一个mapper.xml都需要在mybatis核心配置文件中注册
解决方法:
在mybatis核心配置文件中注册 Eg:
<mappers> <mapper resource="com/song/dao/UserMapper.xml"></mapper> </mappers>
2.CRUD(练手)
Mapper.xml中 :
**namespeace : dao层接口名 标签中 id : 接口中的方法名 **
parameterType : 参数类型 resultType :结果返回值类型
每个sqlSession 对象用完后都应关闭,建议写在finally中
1. 需要提交事务
1.insert
- 编写接口中的方法
public interface UserMapper {
//添加用户
int addUser(User user);
}
- 编写Mapper.xml文件
<!-- 插入用户-->
<insert id="addUser" parameterType="com.song.pojo.User" >
insert into mybatis.user(id,name, pwd) VALUES (#{id},#{name},#{pwd})
</insert>
- 测试
@Test
public void insertUser(){
//获取SqlSession
SqlSession sqlSession = mybatisUtil.getSqlSession();
//获取接口映射对象 Mapper对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//执行接口中的方法
int i = mapper.addUser(new User(8,"sjf","123"));
if (i>0){
System.out.println("插入成功");
sqlSession.commit();
}else {
System.out.println("插入失败");
sqlSession.rollback();
}
sqlSession.close();
}
2.delete
- 编写接口中的删除用户方法
public interface UserMapper {
//删除用户
int deleteUser(int id);
}
- 编写Mapper.xml文件
<!-- 删除用户-->
<delete id="deleteUser" parameterType="int" >
delete from mybatis.user where id=#{id}
</delete>
- 测试
@Test
public void insertUser(){
//获取SqlSession
SqlSession sqlSession = mybatisUtil.getSqlSession();
//获取接口映射对象 Mapper对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//执行接口中的方法
int i = mapper.deleteUser(8);
if (i>0){
System.out.println("删除成功");
sqlSession.commit();
}else {
System.out.println("删除失败");
sqlSession.rollback();
}
sqlSession.close();
}
3.update
public interface UserMapper {
//修改用户
int modifyUser(User user);
}
<!-- 修改用户-->
<update id="modifyUser" parameterType="com.song.pojo.User">
update mybatis.user
set name =#{name} ,pwd=#{pwd}
where id=#{id};
</update>>
@Test
public void modifyUser(){
SqlSession sqlSession = mybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.modifyUser(new User(8, "hhh", "123"));
if (i>0){
System.out.println("修改成功");
sqlSession.commit();
}else {
System.out.println("修改失败");
sqlSession.rollback();
}
sqlSession.close();
}
2.不需提交事务
4.select
- 编写接口中查询方法
//获取用户列表
List<User> getUserList();
//通过id获取用户
User getUserById(int id);
- 配置Mapper.xml文件
<!-- 查询用户-->
<select id="getUserList" resultType="com.song.pojo.User">
select * from mybatis.user
</select>
<!-- 通过ID 查询用户-->
<select id="getUserById" parameterType="int" resultType="com.song.pojo.User">
select * from mybatis.user where `id`=#{id}
</select>
- 测试
@Test
public void getUser(){
SqlSession sqlSession=null;
try {
//获取SqlSession对象
sqlSession = mybatisUtil.getSqlSession();
//获取接口映射对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//通过接口映射对象,执行接口中的方法
// 执行方法 1
List<User> userList = mapper.getUserList();
// 执行方法 2
// List<User> userList = sqlSession.selectList("com.song.dao.UserMapper.getUserList");
for (User user : userList) {
System.out.println(user);
}
}finally {
//关闭sqlSession资源
sqlSession.close();
}
}
//通过id获取用户
@Test
public void getUserById(){
User user=new User(1,"aaa","sdww");
SqlSession sqlSession = mybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(2);
System.out.println(userById);
sqlSession.close();
}
3.小结
易错点:
-
标签匹配错误:
org.apache.ibatis.binding.BindingException: Mapper method 'com.song.dao.UserMapper.addUser attempted to return null from a method with a primitive return type (int).
eg:
<!-- 插入用户 正确--> <insert id="addUser" parameterType="com.song.pojo.User" > insert into mybatis.user(id,name, pwd) VALUES (#{id},#{name},#{pwd}) </insert> <!-- 插入用户 错误--> <select id="addUser" parameterType="com.song.pojo.User" > insert into mybatis.user(id,name, pwd) VALUES (#{id},#{name},#{pwd}) </select>
-
resource绑定Mapper.xml 需要用"/"
eg:
<mapper resource="com/song/dao/UserMapper.xml"/>
3. mybatis配置解析
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
1. 核心配置文件
-
mybatis-config.xml
<?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"> <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> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
1. environments(环境配置)
environments 元素定义了如何配置环境。
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
-
注意一些关键点:
-
默认使用的环境 ID(比如:default=“development”)。
-
每个 environment 元素定义的环境 ID(比如:id=“development”)。
-
事务管理器的配置
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
默认是JDBC
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
提示 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们
-
数据源的配置(比如:type=“POOLED”)。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
默认是POOLED
-
2.mappers (映射器)
注册绑定 dao层 mapper.xml
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
已过时
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
接口名必须和Mapper.xml文件同名
接口名必须和Mapper.xml在同一个包中
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
接口名必须和Mapper.xml文件同名
这些配置会告诉 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了
3.properties(属性)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置
eg:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=utf-8&useSSL=true
username=root
password=123456
在xml核心配置文件中引入propert文件
<!-- 引入外部配置文件-->
<properties resource="mybatis.properties"/>
注意 xml标签中元素是有顺序的:The content of element type “configuration” must match “(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”.
也可以在 properties 元素的子元素中设置。例如:
<properties resource="mybatis.properties">
<property name="password" value="123456"/>
</properties>
两者优先级: properties文件 > property标签
<?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>
<!-- 引入外部配置文件-->
<properties resource="mybatis.properties"/>
<!--环境s-->
<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>
<!--没有的话会出现绑定注册异常
org.apache.ibatis.binding.BindingException: Type interface
com.song.dao.UserDao is not known to the MapperRegistry.mapper
-->
<mappers>
<package name="com.song.dao"/>
</mappers>
</configuration>
4.typeAliases(类型别名)
意在降低冗余的全限定类名书写
1.类型别名可为 Java 类型设置一个缩写名字
eg: 把 com.song.pojo.User 缩写为 User
<typeAliases>
<typeAlias type="com.song.pojo.User" alias="User"></typeAlias>
</typeAliases>
2.指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
eg:
<typeAliases>
<!-- <typeAlias type="com.song.pojo.User" alias="User"></typeAlias>-->
<package name="com.song.pojo"/>
</typeAliases>
小结: 第一种适合实体类比较少的时候,第二种则反之
第一种可以自定义别名,
第二种自定义别名则需要在实体类加@alias注解
5.其他配置
settings(设置)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
4.结果映射
引入:当数据库中字段名和实体类中属性名不一致时
数据库字段:
实体类属性:
查询后密码为空
这时就需要就需要一种映射关系将数据库字段和实体类属性映射关联
// id:结果映射标识 type:结果类型
<resultMap id="UserMap" type="User"
// 不一致的内容 property:实体类属性 column:数据库字段
<result property="password" column="pwd"/>
</resultMap>
// resultMap:引入结果映射
<select id="getUserById" parameterType="int" resultMap="UserMap">
select *
from mybatis.user
where id = #{id}
</select>
映射之后查询:
5.日志
1.日志工厂
在核心配置文件的settings中设置
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
---|---|---|---|
设置名 | 描述 | 有效值 | 默认值 |
-
STDOUT_LOGGING日志工厂
在核心配置文件中设置(注意xml文件中标签元素的位置)
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
打印出的日志
-
LOG4J日志工厂
-
在pom文件中导入log4j依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
在mybatis-config.xml核心配置文件中setting中设置
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
打印出的日志
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xojW1N1D-1622602332291)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20210524183554031.png)]
6.分页
1. 通过sql
- 编写接口
2.通过RowBounds
3.通过第三方插件
7.使用注解开发
-
在mybatis-config.xml核心配置文件中配置映射
可使用类绑定和包扫描
<mappers>
<!-- <mapper class="com.song.dao.UserMapper"/>-->
<package name="com.song.dao"/>
<!-- 注解不能使用 <mapper resource="com/song/dao/UserMapper"/>-->
<!-- 注解不能使用 <mapper url="com.song.dao.UserMapper"/>-->
</mappers>
-
编写接口
public interface UserMapper { //查询用户 @Select("select * from user") List<User> getUserList(); //通过id查询用户 @Select("select * from user where id =#{id}") User getUser(int id); //增加用户 @Insert("insert into user(name, pwd) VALUES (#{name},#{pwd})") int addUser(User user); //删除用户 //当有多个基本数据类型应使用@Param注解 取值与注解相对应 @Delete("delete from user where id=#{id1}") int deleteUser(@Param("id1") int id); //修改用户 @Update("update user set name=#{name} , pwd=#{pwd}") int modifyUser(User user); }
当有多个基本数据类型应使用@Param注解取值与注解相对应
-
编写测试类
public class UserMapperTest { //获取用户列表 @Test public void getUserList() { SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } sqlSession.close(); } //通过Id查询用户 @Test public void getUser() { SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUser(1); System.out.println(user); sqlSession.close(); } //添加用户 @Test public void addUser() { SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.addUser(new User(3, "宋俊芳", "123")); if (i > 0) { System.out.println("添加成功!!"); sqlSession.commit(); } else { System.out.println("添加失败!!"); } sqlSession.close(); } //删除用户 @Test public void deleteUser() { SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.deleteUser(11); if (i > 0) { System.out.println("删除成功!!"); sqlSession.commit(); } else { System.out.println("删除失败!!"); } sqlSession.close(); } //修改用户信息 @Test public void modifyUser() { SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.modifyUser(new User(0, "小明明", "234")); if (i > 0) { System.out.println("修改成功!!!"); sqlSession.commit(); } else { System.out.println("修改失败!!!"); } sqlSession.close(); } }
8. #{} 与 ${}
-
#{}是预编译处理,$ {}是字符串替换。
-
在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;mybatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
-
使用 #{} 可以有效的防止SQL注入,提高系统安全性。
${param}传递的参数会被当成sql语句中的一部分,比如传递表名,字段名
例子:(传入值为id)
order by ${param}
则解析成的sql为:
order by id
#{parm}传入的数据都当成一个字符串,会对自动传入的数据加一个双引号
例子:(传入值为id)
select * from table where name = #{param}
则解析成的sql为:
select * from table where name = "id"
为了安全,能用#的地方就用#方式传参,这样可以有效的防止sql注入攻击
sql注入简介
直接上了百度的例子,感觉一看就清晰明了
某个网站的登录验证的SQL查询代码为:
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
恶意填入
userName = "1' OR '1'='1";
与passWord = "1' OR '1'='1";
时,将导致原本的SQL字符串被填为
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
也就是实际上运行的SQL命令会变成下面这样的
strSQL = "SELECT * FROM users;"
这样在后台帐号验证的时候巧妙地绕过了检验,达到无账号密码,亦可登录网站。所以SQL注入攻击被俗称为黑客的填空游戏
9.lombok
- 安装lombok插件
- 在大项目中导入lombok依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
-
注解
写在类上
- @Data
-
@AllArgsConstructor有参构造
-
@NoArgsConstructor 无参构造
10.进阶
1.多对一嵌套查询
如: 多个学生对应一个老师
应用 resultMap 结果映射 association 关联
//Student类
public class Student {
private int id;
private String name;
//外键
//学生关联老师
private Teacher teacher;
}
//Teacher类
public class Teacher {
private int id;
private String name;
}
eg:查询学生信息 id、 name 及教师姓名
1. 按照结果嵌套映射处理
-
编写dao层 Mapper接口
public interface StudentMapper { //获取学生列表 List<Student> getStudentList(); }
-
编写Mapper.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.song.dao.StudentMapper"> <!-- 方法名 结果映射标识名 --> <select id="getStudentList" resultMap="Student-Teacher"> select s.id sid, s.name sname, t.name tname from student as s, teacher as t where s.tid = t.id </select> <!--按照结果嵌套映射处理 多对一 关键字 association 关联--> <!-- 结果映射--> <!-- 结果映射标识名 结果返回值类型--> <resultMap id="Student-Teacher" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"/> </association> </resultMap> </mapper>
-
测试
public class StudentMapperTest { @Test //获取学生列表 public void getStudentList(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> students = mapper.getStudentList(); for (Student student : students) { System.out.println(student); } sqlSession.close(); } }
2.按照查询嵌套映射处理
-
编写dao层 Mapper接口
public interface StudentMapper { //获取学生列表 List<Student> getStudentList(); //获取学生列表2 List<Student> getStudentList2(); }
-
编写Mapper.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.song.dao.StudentMapper"> <select id="getStudentList2" resultMap="Student-Teacher2"> select * from mybatis.student </select> <!--结果映射--> <!-- 结果映射标识名 结果返回值类型 --> <resultMap id="Student-Teacher2" type="Student"> <!-- 实体属性名 数据库字段名 --> <result property="id" column="id"/> <result property="name" column="name"/> <!-- 实体类型 --> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> <!--嵌套查询--> </resultMap> <!--嵌套查询表示名 返回值类型 --> <select id="getTeacher" resultType="Teacher"> select * from mybatis.teacher where id=#{tid} </select> </mapper>
-
测试
@Test //获取学生列表2 public void getStudentList2(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentList2 = mapper.getStudentList2(); for (Student student : studentList2) { System.out.println(student); } }
2.一对多嵌套查询
pojo:
//Student类
public class Student {
private int id;
private String name;
//所属的教师
private Teacher teacher;
}
//Teacher类
public class Teacher {
private int id;
private String name;
//老师包含的学生
private List<Student> students;
}
查询Teacher 所管理的学生
复杂的属性 应用 resultMap 结果映射 collection 关联
javaType: 指定属性的类型
ofType: 集合中泛型的类型
1. 按照结果嵌套映射处理
-
编写dao层Mapper接口
public interface TeacherMapper { //获取老师包含的信息 List<Teacher> getTeacherList(@Param("tid") int id); }
-
编写mapper.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.song.dao.TeacherMapper"> <!--结果映射 标识名 对应id--> <select id="getTeacherList" resultMap="Teacher-Student"> select s.id sid, s.name sname, t.name tname from teacher t, student s where s.tid = t.id and t.id = #{tid} </select> <!--结果映射--> <!-- 结果映射标识名 结果返回值类型 --> <resultMap id="Teacher-Student" type="Teacher"> <result property="name" column="name"/> <!-- 实体类属性名 ofType集合泛型中类型 --> <collection property="students" ofType="Student"> <!--实体类属性名 sql中所查询的字段 --> <result property="id" column="sid"/> <result property="name" column="sname"/> </collection> </resultMap> </mapper>
-
测试
@Test public void getTeacherList(){ SqlSession sqlsession = MybatisUtil.getSqlsession(); TeacherMapper mapper = sqlsession.getMapper(TeacherMapper.class); List<Teacher> teacherList = mapper.getTeacherList(2); for (Teacher teacher : teacherList) { System.out.println(teacher); } }
2. 按照查询嵌套映射处理
-
编写dao层Mapper接口
public interface TeacherMapper { //获取老师包含的信息方法一 List<Teacher> getTeacherList(@Param("tid") int id); //获取老师包含的信息方法二 List<Teacher> getTeacherList2(@Param("tid") int id); }
-
编写mapper.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.song.dao.TeacherMapper"> <select id="getTeacherList" resultMap="Teacher-Student"> select s.id sid, s.name sname, t.name tname from teacher t, student s where s.tid = t.id and t.id = #{tid} </select> <resultMap id="Teacher-Student" type="Teacher"> <result property="name" column="name"/> <collection property="students" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> </collection> </resultMap> <select id="getTeacherList2" resultMap="Teacher-Student2"> select *from mybatis.teacher where id=#{tid} </select> <resultMap id="Teacher-Student2" type="Teacher"> <collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/> </resultMap> <select id="getStudent" resultType="Student"> select * from mybatis.student where tid=#{tid} </select> </mapper>
-
测试
public class TeacherMapperTest { @Test public void getTeacherList() { SqlSession sqlsession = MybatisUtil.getSqlsession(); TeacherMapper mapper = sqlsession.getMapper(TeacherMapper.class); List<Teacher> teacherList = mapper.getTeacherList(2); for (Teacher teacher : teacherList) { System.out.println(teacher); } sqlsession.close(); } @Test public void getTeacherList2() { SqlSession sqlsession = MybatisUtil.getSqlsession(); TeacherMapper mapper = sqlsession.getMapper(TeacherMapper.class); List<Teacher> teacherList2 = mapper.getTeacherList2(1); for (Teacher teacher : teacherList2) { System.out.println(teacher); } sqlsession.close(); } }
小结:
-
多对一 : association(关联)
-
一对多 : collection(集合)
-
javaType & ofType
- javaType :指定Java实体类属性的类型
- ofType : 指定集合中泛型的类型
11.动态SQL
-
什么时动态SQL:
根据不同场景选用不同的SQL语句
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
---|---|---|---|
默认值 |
在mybatis-config.xml核心配置文件中设置
<!-- 是否开启驼峰命名自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<typeAlias type="com.song.pojo.Blog" alias="Blog"/>
0.if标签
-
编写dao层mapper接口
public interface BLogMapper { //添加Blog int addBlog(Blog blog); //动态查询Blog If List<Blog> queryBlogIf(Map map); }
-
编写mapper.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.song.dao.BLogMapper"> <insert id="addBlog" parameterType="Blog"> insert into mybatis.blog(id, title, author, create_time, views) values (#{id}, #{title}, #{ author}, #{createTime}, #{views}); </insert> <select id="queryBlogIf" parameterType="map" resultType="Blog"> <!-- where 1=1 不规范 --> select * from mybatis.blog where 1=1 <if test="title!=null"> and title=#{title} </if> <if test="author!=null"> and author=#{author} </if> </select> </mapper>
-
测试
public void queryBlogIf(){ SqlSession sqlsession = MybatisUtil.getSqlsession(); BLogMapper mapper = sqlsession.getMapper(BLogMapper.class); Map map = new HashMap(); // map.put("title","Mybatis如此简单"); map.put("author","sjf"); List<Blog> blogs = mapper.queryBlogIf(map); for (Blog blog : blogs) { System.out.println(blog); } sqlsession.close(); }
1.where标签
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
eg:
改造 0. if 标签
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<if test="title!=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</where>
</select>
2.常用标签
1.choose
choose、when、otherwise
otherwise: 仅当 when中不成立时才会选择
when : 当满足以一个when时则不会再次选择其余的when 只会选择一个执行
-
编写dao层mapper接口
//动态查询Blog choose List<Blog> queryBlogChoose(Map map);
-
编写mapper.xml配置文件
<select id="queryBlogChoose" resultType="Blog" parameterType="map"> select * from mybatis.blog <where> <choose> <when test="title!=null"> and title=#{title} </when> <when test="author!=null"> and author=#{author} </when> <otherwise> and views=#{views} </otherwise> </choose> </where> </select>
-
测试
//动态查询Blog choose @Test public void queryBlogChoose() { SqlSession sqlsession = MybatisUtil.getSqlsession(); BLogMapper mapper = sqlsession.getMapper(BLogMapper.class); Map map = new HashMap(); map.put("views",10000); map.put("title", "Spring如此简单"); // map.put("author", "狂神说"); List<Blog> blogs = mapper.queryBlogChoose(map); for (Blog blog : blogs) { System.out.println(blog); } sqlsession.close(); }
2.set
-
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
-
set 元素会动态地在行首插入 SET 关键字 ,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的) 故set元素中必须有正确的sql语句。
eg:更新Blog
-
编写dao层mapper接口
//动态更新Blog set int updateBlogSet(Map map);
-
编写mapper.xml配置文件
<update id="updateBlogSet" parameterType="map"> update mybatis.blog <set> <if test="title!=null"> title=#{title}, </if> <if test="author!=null"> author=#{author}, </if> </set> <where> id=#{id} </where> </update>
-
测试
//动态更新Blog choose @Test public void queryBlogSet() { SqlSession sqlsession = MybatisUtil.getSqlsession(); BLogMapper mapper = sqlsession.getMapper(BLogMapper.class); Map map = new HashMap(); map.put("title", "Spring如此简单1"); map.put("author", "sjf"); map.put("id","d1d1f1dd5b1a4661baccba8e88c8d3ec"); mapper.updateBlogSet(map); sqlsession.commit(); sqlsession.close(); }
-
-
3.foreach标签
格式:
<select id="getBlogListForEach" parameterType="map" resultType="Blog">
select * from mybatis.blog
# 注释 select * from blog where( id=1 or id=2 or id=3 or id=4)
<where>
<!-- collection从哪里(集合)遍历
item被遍历集合中的元素
open以```开始
close 以```结束
separator 通过什么来分割 -->
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
eg:查询Blog id为1,2,3,4的blog
-
编写dao层mapper接口
//动态查询遍历Blog forEach List<Blog> getBlogListForEach(Map map);
-
编写mapper.xml配置文件
<select id="getBlogListForEach" parameterType="map" resultType="Blog"> select * from mybatis.blog # 注释 select * from blog where( id=1 or id=2 or id=3 or id=4) <where> <!-- collection从哪里(集合)遍历 item被遍历集合中的元素 open以```开始 close 以```结束 separator 通过什么来分割 --> <foreach collection="ids" item="id" open="(" close=")" separator="or"> id=#{id} </foreach> </where> </select>
-
测试
@Test public void getBlogForEach(){ SqlSession sqlsession = MybatisUtil.getSqlsession(); BLogMapper mapper = sqlsession.getMapper(BLogMapper.class); HashMap map = new HashMap(); ArrayList ids = new ArrayList(); ids.add("1"); ids.add("2"); ids.add(3); ids.add(4); map.put("ids",ids); for (Object id : ids) { System.out.println(id.getClass()); } List<Blog> blogs = mapper.getBlogListForEach(map); for (Blog blog : blogs) { System.out.println(blog); } sqlsession.close(); }
4.小结
- 动态SQL:不同情况下可以执行不同的SQL语句
- 本质上是SQL语句逻辑的拼接
- 使用时:
- 先写出应要执行的SQL语句(确保无误)
- 在mapper.xml中根据逻辑进行组装所需要的SQL语句
12.缓存
1.一级缓存
默认开启
基于SqlSession级别
查询相同数据
只执行一条SQL
Opening JDBC Connection
Created connection 2101636817.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7d446ed1]
==> Preparing: select * from mybatis.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
=================
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7d446ed1]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7d446ed1]
Returned connection 2101636817 to pool.
刷新缓存
- 两条相同查询语句间插入删除,更新,插入语句会导致缓存刷新
- 手动清理缓存(缓存失效)
eg:
Opening JDBC Connection
Created connection 315805187.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@12d2ce03]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
==> Preparing: update mybatis.user set name=? where id=?
==> Parameters: 宋俊芳(String), 1(Integer)
<== Updates: 1
=================
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 宋俊芳, 234
<== Total: 1
User(id=1, name=宋俊芳, pwd=234)
Rolling back JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@12d2ce03]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@12d2ce03]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@12d2ce03]
Returned connection 315805187 to pool.
查询两条不同数据
执行两条SQL(查询多条不同数据执行多条SQL)
Opening JDBC Connection
Created connection 1186339926.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@46b61c56]
==> Preparing: select * from mybatis.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
=================
==> Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<== Columns: id, name, pwd
<== Row: 2, 李四, 234
<== Total: 1
User(id=2, name=李四, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@46b61c56]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@46b61c56]
Returned connection 1186339926 to pool.
若关闭使用二级缓存
Opening JDBC Connection
Created connection 1902430796.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
=================
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Returned connection 1902430796 to pool.
2.二级缓存
基于nameSpeace级别
-
在mybatis-config.xml中设置
<!-- 显示开启缓存 二级\全局--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
-
开启缓存 在Mapper.xml中
<cache/>
遇到问题
org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.song.pojo.User
解决方案: 序列化实体类 实现Serializable接口
测试:
实体类
//实现Serializable接口序列化
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
测试
//通过id获取用户
@Test
public void getUserById() {
SqlSession sqlsession = MybatisUtil.getSqlsession();
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
User u1 = mapper.getUserById(1);
System.out.println(u1);
sqlsession.close();//sqlsession关闭
sqlsession.clearCache();//清除一级缓存
System.out.println("=================");
SqlSession sqlsession2 = MybatisUtil.getSqlsession();
UserMapper mapper2 = sqlsession2.getMapper(UserMapper.class);
User u2 = mapper2.getUserById(1);
System.out.println(u2);
sqlsession2.close();//sqlsession2关闭
}
输出的日志:
同一个Mapper中的不同sqlSession也可共用二级缓存
Opening JDBC Connection
Created connection 2049051802.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
Returned connection 2049051802 to pool.
=================
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [com.song.dao.UserMapper]: 0.5
User(id=1, name=张三, pwd=234)
如关闭使用缓存
<!-- 通过id获取用户 关闭使用缓存 -->
<select id="getUserById" parameterType="_int" resultType="User" useCache="false">
select *
from mybatis.user
where id = #{id}
</select>
共查询两次:
Opening JDBC Connection
Created connection 1902430796.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Returned connection 1902430796 to pool.
=================
Opening JDBC Connection
Checked out connection 1902430796 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Returned connection 1902430796 to pool.
3.缓存原理
ectionImpl@7a220c9a]
Returned connection 2049051802 to pool.
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [com.song.dao.UserMapper]: 0.5
User(id=1, name=张三, pwd=234)
如关闭使用缓存
```xml
<!-- 通过id获取用户 关闭使用缓存 -->
<select id="getUserById" parameterType="_int" resultType="User" useCache="false">
select *
from mybatis.user
where id = #{id}
</select>
共查询两次:
Opening JDBC Connection
Created connection 1902430796.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Returned connection 1902430796 to pool.
=================
Opening JDBC Connection
Checked out connection 1902430796 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 张三, 234
<== Total: 1
User(id=1, name=张三, pwd=234)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7164ca4c]
Returned connection 1902430796 to pool.