mybatis框架 持久层
Spring
Spring MVC 表示层
shiro 安全框架
git 版本控制工具
项目阶段
Junit
使用Junit做单元测试的目的:
-
方便运行代码,人为查看结果符不符合预期
-
使用断言测试让机器去判断结果符不符合预期,a开头的很多junit断言测试
User one = UserDao.getOne(1); asserEquals(null,one);
@Test注释的方法成为测试用例
测试注解使用的要求:
-
要注解在某个方法上
-
该方法要求无参,返回值为空,且为public
在test里面添加参数
@Test(timeout=1000)
限时测试:timeout属性指定毫秒值,要求该单元测试用例在规定时间内完成
@Before
作用于某个方法上
作用:所有的测试单元执行之前执行该方法,每执行一个测试单元就会执行一次Before
要求:
-
要注解在某个方法上
-
该方法要求无参,返回值为空,且为public
@After
作用于某个方法上
作用:在所有单元测试用例之后执行该方法(多用于资源回收)
要求:
-
要注解在某个方法上
-
该方法要求无参,返回值为空,且为public
@BeforeClass
在整个测试类的所有测试用例运行之前运行一次
-
要注解在某个方法上
-
该方法要求无参,返回值为空,还要被public static修饰!!
@AfterClass
在整个测试类的所有测试用例运行完之后运行一次
-
要注解在某个方法上
-
该方法要求无参,返回值为空,还要被public static修饰!!
@Ignore
忽略这个测试用例,但是会在总计结果中显示统计信息
@Runwith()
套件测试,同时测试多个测试类。所谓Runner提供单元你测试运行的环境,默认为Junit4.class
@RunWith(Suite.class) @Suite.SuiteClasses(UserDaoImplTest.class,AppTest.class)
@Suite.SuiteClasses指定该套件中包含了哪些测试类
1.简介
-
MyBatis 是一款优秀的持久层框架
-
它支持自定义 SQL、存储过程以及高级映射。
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
-
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
1.1.获取mybaties
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
-
github
1.2.持久层
Dao层(数据库操作层),Service层(业务层),Control层(专门接收用户请求层)。
-
持久层:完成持久化工作的代码块
-
层是界限十分明显的
持久化是一个动作,持久层是一个概念。
1.3.为什么需要Mybatis
-
方便
-
传统的JDBC太复杂,简化,框架,自动化
-
帮助程序员将数据存入到数据库中
-
不用mybatis也可以,更容易上手
-
用的人特别多
spring,spring mvc,springboot
2.第一个mybatis程序
步骤:搭建环境-》导入mybatis-》编写代码-》测试
搭建数据库环境
2.1导入xml依赖
<dependencies> <!-- mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <!-- 测试工具junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies>
2.2.创建一个maven子项目
-
配置mybatis的核心配置文件
<?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核心配置文件--> <configuration> <!-- environments配置多个环境--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <!-- 在xml中&符号需要进行转义&--> <property name="url" value="jdbc:mysql:///mybatis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> </configuration>
2.3编写mybatis工具类
//第一步从 XML 中构建 SqlSessionFactory public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //第二步既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
2.4编写代码
-
实体类
public class User { private int id; private String name; private String password; public int getId() { return id; } public void setId(int 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; } public User() { } public User(int id, String name, String password) { this.id = id; this.name = name; this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; } }
-
Dao接口
public interface UserDao { List<User> getUserList(); }
-
接口实现类
<?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命名空间绑定一个Maper接口--> <mapper namespace="com.liu.Dao.UserDao"> <!-- id对应UserDao的方法,resultType对应实体类--> <select id="getUserList" resultType="com.liu.Pojo.User"> select * from mybatis.user </select> </mapper>
2.5测试
注意:
org.apache.ibatis.binding.BindingException: Type interface com.liu.Dao.UserMaper is not known to the MapperRegistry.
MapperRegistry是什么?
核心配置文件中注册mapers
-
junit测试
public class UserDaoTest { @Test public void test() { //第一步获取sqlSession对象 SqlSession sqlSession = MybatisUtil.getSqlSession(); //执行SQL //方式一getMapper UserMaper mapper = sqlSession.getMapper(UserMaper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } //关闭sqlSession sqlSession.close(); } }
可能遇到的问题:
1.配置文件没有注册
2.绑定接口错误
3.方法名不对
4.返回类型不对
5.Maven导出资源问题
3.CRUD
1.namespace
namespace中的包名要和Mapper接口的报名一致
2.select
选择,查询语句;
-
id:就是对应namespace中的方法名
-
resulType:sql语句执行的返回值!
-
paramterType:参数类型!
1.编写接口
2.编写对应的mapper中的sql
3.测试
3.Insert
UserMapper:
//插入一条记录 public void insertOne(User user);
UserMapper.xml
<insert id="insertOne" parameterType="com.liu.Pojo.User"> insert into user values (#{id},#{name},#{password}) </insert>
Test:
//插入用户 @Test public void insertOne(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.insertOne(new User(4,"liulang","123456")); //提交事务 sqlSession.commit(); sqlSession.close(); }
4.Update
UserMapper:
//修改记录 public int alterUser(User user);
UserMapper.xml
<update id="alterUser" parameterType="com.liu.Pojo.User"> update user set name=#{name},password=#{password} where id=#{id}; </update>
Test:
//修改信息 @Test public void alterUser(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.alterUser(new User(4, "liulang", "123")); if (i>0) System.out.println("修改成功"); else System.out.println("修改失败"); //提交事务 sqlSession.commit(); sqlSession.close(); }
5.Delete
UserMapper:
//删除用户 public int deleteUser(int id);
UserMapper.xml
<delete id="deleteUser" parameterType="int"> delete from user where id=#{id} </delete>
Test:
//删除用户 @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.deleteUser(4); if (i>0) System.out.println("删除成功"); else System.out.println("删除失败"); //提交事务 sqlSession.commit(); sqlSession.close(); }
注意:增删改都需要提交事务才会生效!!! sqlSession.commit();
6.错误
-
namespace="com.liu.Mapper.UserMapper";必须写详细路径
-
sql语句标签错误
-
注册mapper;resource的路径要用/而不是点
<mappers> <mapper resource="com/liu/Mapper/UserMapper.xml"/> </mappers>
4.maven资源导出问题
<build> <resources> <!-- 设置正常情况的resources目录下的properties文件--> <resource> <!-- 配置路径--> <directory>src/main/resources</directory> <includes> <!-- 包含什么文件--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> <!-- 设置java路径的properties文件--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> </resources> </build>
7.非常实用的Map
如果我们的实体类或数据中的表,字段过多,我们考虑使用Map。
修改或添加部分信息:
UserMapper:
//使用map修改部分数据 public int alterUser2(Map<String,Object> map);
UserMapper.xml
<update id="alterUser2" parameterType="map"> update user set name=#{username} where id=#{userid}; </update>
Test:
//使用map更新部分信息 @Test public void alterUser2(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("username","李小盛"); map.put("userid",3); int i = mapper.alterUser2(map); if (i>0) System.out.println("修改成功"); else System.out.println("修改失败"); //提交事务 sqlSession.commit(); sqlSession.close(); }
根据多个条件查询信息:
UserMapper:
//指定多条件查询 public User getUserById2(Map<String,Object> map);
UserMapper.xml
<select id="getUserById2" parameterType="map" resultType="com.liu.Pojo.User"> select * from user where id=#{id} and name=#{name} </select>
Test:
@Test public void getUserById2(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("id",3); map.put("name","李小盛"); User user = mapper.getUserById2(map); System.out.println(user); sqlSession.close(); }
Map传递参数,直接在sql中取出key即可。
使用Map的时候一定要注意map里面的键值对必须和UserMapper.xml中使用的变量对应!!!
对象传递参数,直接在sql中去对象的属性。
只有一个基本数据类型参数的情况下,可以直接在sql中使用
多个参数用map或注解!
8.模糊查询
1.java执行的时候传递通配符%
List<User> users = mapper.likeUser("%李%");
2.在sql拼接中使用通配符
<select id="likeUser" resultType="com.liu.Pojo.User"> select * from user where name like "%"#{name}"%" </select>
第二种写法:
<select id="queryAll" resultMap="USER" parameterType="map"> select * from user <where> <if test="name!=null"> name like '%${name}%' </if> </where> </select>
4.XML配置
1.核心配置文件
mybaits-config,xml
-
environments(环境配置)
-
environment(环境变量)
-
transactionManager(事务管理器)
-
dataSource(数据源)
-
-
2.环境配置
Mybatis可以配置成适应多环境
注:尽管可以配置多个环境,但是每个sqlSessionFactory实例只能选择一种环境
学会使用配置多套运行环境!
Mybatis默认的事务管理器就是JDBC,连接池:POOLED
3.属性(properties)
通过propertis来引入配置文件
1.编写一个配置文件:
driver=com.mysql.jdbc.Driver url=jdbc:mysql:///mybatis username=root password=123456
2.在核心配置文件中引入:在核心配置文件中所有的标签都有规定的先后顺序
资源在核心配置同一个路径下可以使用相对路径定位
没有添加额外参数 <properties resource="db.properties"/> 添加额外参数 <properties resource="db.properties"> <property name="username" value="root1"/> <property name="password" value="123456"/> </properties>
-
可以直接引入配置文件
-
可以在其中增加一些配置属性
-
如果两种方式有相同的属性,优先使用外部配置文件的属性!!!
4.类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
第一种方式:
<typeAliases> <typeAlias type="com.liu.Pojo.User" alias="User"/> </typeAliases>
第二种方式
可以指定一个包名,Mybatis会在这个包名下面搜索需要的JavaBean,比如:
扫描的实体类的名称,它的默认别名就是这个类的首字母小写。
<typeAliases> <package name="com.liu.Pojo"/> </typeAliases>x
在实体类比较少的情况使用第一种,
如果实体类十分多的时候,建议第二种
第一种可以DIY别名,第二种不行。如果非要在实体类上增加注解,注解方式大于默认搜索
@Alias("hello") public class User(){};
5.映射器(mappers)
MapperRegistry:注册绑定我们需要的Mapper文件
方式一:【推荐使用】
<mappers> <mapper resource="com/liu/Mapper/UserMapper.xml"/> </mappers>
方式二:使用class方式绑定注册
<mappers> <mapper class="com.liu.Mapper.UserMapper"/> </mappers>
方式三:使用包扫描注册绑定
<mappers> <package name="com.liu.Mapper"/> </mappers>
方式二和方式三注意!!:
-
接口和它的Mapper配置文件必须同名
-
接口和它的Mapper配置文件必须在同一个包下面
6.生命周期
生命周期类和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
-
一旦创建了SqlSessionFactory就不在需要他了
-
局部变量
SqlSessionFactory:
-
可以想象为数据库连接池
-
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
-
因此 SqlSessionFactory 的最佳作用域是应用作用域。(程序开始他就开始,程序结束他就结束)
-
简单的就是使用单例模式或者静态单例模式。
SqlSession:
-
连接到连接池的一个请求!
-
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
-
用完之后赶紧关闭,否则资源被占用
每一个Mapper代表一个具体的业务。
5.解决属性名和字段名不一致的问题
解决方法:
-
起别名
<select id="getUserById" parameterType="int" resultType="com.liu.Pojo.User"> select id,name,password as pwd from user where id = #{id} </select>
2.resultMap
结果集映射
id name password id name pwd
resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets
数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
<!-- resultMap就是使用这里的id,类型映射为实体类 --> <resultMap id="UserMap" type="User"> <!-- property表示实体类属性,column表示数据库字段映射。将字段映射为实体类的对应属性 --> <result property="id" column="id"/> <result property="name" column="name"/> <result property="pwd" column="password"/> </resultMap> <select id="getUserList" resultMap="UserMap"> select * from user </select>
6.日志
6.1日志工厂
如果一个数据库操作出现了异常,我们需要排错。日志就是我们最好的助手。
曾经:sout、debug
现在:日志工厂!
-
SLF4J
-
LOG4J【掌握】
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING【掌握】
-
NO_LOGGING
在Mybatis中具体使用哪一个日志实现,在设置中设定!
STDOUT_LOGGING:标准日志输出
配置设置的时候一定要注意格式、大小写、空格等问题
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
6.2Log4j
Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。
-
日志可以输出到控制到或GUI组件
-
我们可以控制每一条日志输出的格式
-
通过设置每一条日志的级别,我们能够更细致的控制日志生成的过程。
-
通过配置文件来进行配置,不需要我们修改应用的代码
1.导入LOG4J的包
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2.log4j.properties
### 配置根 ### #配置日志类型 log4j.rootLogger = debug,console ,file ### 设置输出sql的级别,其中logger后面的内容全部为jar包中所包含的包名 ### log4j.logger.org.apache=debug log4j.logger.java.sql.Connection=debug log4j.logger.java.sql.Statement=debug log4j.logger.java.sql.PreparedStatement=debug log4j.logger.java.sql.ResultSet=debug ### 配置输出到控制台 ### log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{ 1 }:%L - %m%n ### 配置输出到文件 ### log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File = ./logs/mybatis.log log4j.appender.file.Append = true log4j.appender.file.Threshold = DEBUG log4j.appender.file.layout = org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n ### 配置输出到文件,并且每天都创建一个文件 ### log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender log4j.appender.dailyRollingFile.File = logs/log.log log4j.appender.dailyRollingFile.Append = true log4j.appender.dailyRollingFile.Threshold = DEBUG log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout log4j.appender.dailyRollingFile.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG #log4j.logger.java.sql.Statement=DEBUG #log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PraparedStatement=DEBUG
3.配置log4j为日志的实现
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
4.Log4j的使用,直接测试运行查询就行了
简单使用
1.在需要使用log4j的类中,导入包,包不要导错了,需要导入apeach的log4j
2.生成日志对象,加载参数为当前类的class
static Logger logger = Logger.getLogger(mybatistest.class);
3.日志级别
logger.info("info:进入testlog4j"); logger.debug("debug:进入testlog4j"); logger.error("error:进入testlog4j");
7.分页
为什么要分页?
-
减少数据的处理量
#{参数名}:按照preparedstatement解析sql语句时所使用的的?占位符
${参数名}:传什么参数,就按字符串拼接方式进行填充
7.1使用limit分页,语法:
select * from user limit statIndex,pagesize; select * from user limit 2,5 select * from user limit 3
使用mybatis实现分页,核心SQl
1.接口
//分页查询 public List<User> limitUser(Map<String,Integer> map);
2.Mapper.xml
<!-- 实现分页查询--> <select id="limitUser" parameterType="map" resultType="user"> select * from user limit #{startIndex},#{pageSize} </select>
3.测试
//分页查询 @Test public void limitUser(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("startIndex",0); map.put("pageSize",3); List<User> users = mapper.limitUser(map); for (User user : users) { System.out.println(user); } }
7.2useGeneratedKeys
如果想要拿到插入数据后自动递增的主键值时,使用useGeneratedKeys=true
KeyColumn:指定自动递增的列明
KeyProperty:指定传入的参数对象中用于存放自动递增的值对应的属性
8.使用注解开发
8.1面向接口编程
-
接口定义与实现的分离
-
接口的本身反应了系统设计人员对系统的抽象理解
-
接口也应有两个类:
-
一个是对一个个体的抽象abstract
-
一个是对个体某个方面的抽象interface
-
8.2使用注解开发
1.注解直接在接口上实现
@Select("select * from user") List<User> getUsers();
2.需要在核心配置文件中绑定接口!
<mappers> <mapper class="com.liu.Mapper.UserMapper2"/> </mappers>
3.测试
//注解方式 @Test public void zjUsers(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); }
本质:反射机制实现
底层:动态代理
8.3CRUD
我们可以在工具类创建的时候实现自动提交事务!
return sqlSessionFactory.openSession(true);
编写接口,增加注解
//方法存在多个参数时,所有的参数前面必须加上@Param("id")注解 @Select("select * from user where id=#{id} and name=#{name}") public User getUserById(@Param("id") int id,@Param("name") String username); @Insert("insert into user values (#{id},#{name},#{password})") public int addUser(User user); @Update("update user set name=#{name},password=#{password} where id=#{id}") int UpdateUser(User user); @Delete("delete from user where id=#{id}") public int delUser(@Param("id") int pid);
【我们必须将接口绑定到注册到我们的核心配置文件中】
<mappers> <package name="com.liu.Mapper"/> </mappers>
关于@Param(")注解
-
基本类型的参数或则String类型的参数需要加上
-
引用数据类型不需要加
-
如果只有一个数据类型的话可以忽略,建议也加上
-
我们在SQL中引用的就是我们这里的@Param设置的属性名
#{}和${}区别:
-
#{}和${}这两个语法是为了动态传递参数而存在的,是Mybatis实现动态SQL的基础
-
#{} 是 占位符 :动态解析 -> 预编译 -> 执行
-
${} 是 拼接符 **:动态解析 -> 编译 -> 执行
类似statement和preparestatement
9.Lombok
-
java library
-
plugs
-
build tools
使用步骤:
1.在IDEA安装plug,Lombok插件
2.在maven中导入Lombok依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
@Data:,toString,GetandSet,hashcode,equals
@AllArgsConstructor @NoArgsConstructor
全参构造和无参构造
3.在实体类上加注解即可
10.多对一
-
对学生来说,关联,多个学生关联一个老师【多对一】
-
对老师而言,集合,一个老师辅导多个学生【一对多】
环境搭建:
1.导入Lombok
2.新建实体类Teacher,Student
3.建立Mapper接口
4.建立Mapper.xml文件
5.在核心配合文件中绑定Mapper接口或文件
6.测试
需求查询所有的学生信息以及对应的老师信息。
1.按照查询嵌套处理
<!-- 使用结果集映射需要使用resultMap--> <select id="getStudent" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="student"> <!-- 复杂的属性,我们需要单独处理--> <!-- 对象使用association--> <result property="id" column="id"/> <result property="name" column="name"/> <association property="teacher" column="id" javaType="Teacher" select="getTeacher"/> <!-- 集合使用collection--> <!-- <collection property=""--> </resultMap> <select id="getTeacher" resultType="teacher"> select * from teacher where id=#{id} </select>
第二条select语句中的where里面的条件#{},花括号里面随便写都不会影响查询结果
2.按照结果嵌套处理
<!-- 按照结果嵌套处理--> <select id="getStudent2" resultMap="StudentTeacher2"> select s.id sid,s.name sname,t.name tname from student as s,teacher as t where s.id=t.id </select> <resultMap id="StudentTeacher2" type="student"> <result column="sid" property="id"/> <result column="sname" property="name"/> <association property="teacher" javaType="Teacher"> <result property="tid" column="id"/> <result property="name" column="tname"/> </association> </resultMap>
查询结果:
Student(id=1, name=liulang, cno=1, teacher=Teacher(id=1, name=chentao, cno=1)) Student(id=2, name=lixiaosheng, cno=1, teacher=Teacher(id=1, name=chentao, cno=1)) Student(id=3, name=zhenghaiqing, cno=1, teacher=Teacher(id=1, name=chentao, cno=1))
11.一对多
一个老师拥有多个学生
1.结果集映射
<select id="getTeacher2" resultMap="TeacherStudent"> select s.id sid,s.name sname,t.name tname,t.id tid,t.cno tcno,s.cno scno from student s,teacher t where s.cno=t.cno </select> <resultMap id="TeacherStudent" type="teacher"> <result property="id" column="tid"/> <result property="cno" column="tcno"/> <result property="name" column="tname"/> <collection property="students" ofType="student"> <result property="id" column="sid"/> <result property="cno" column="scno"/> <result property="name" column="sname"/> </collection> </resultMap>
2.子查询方式
<!-- 子查询--> <select id="getTeacher" resultMap="TeacherStudent1"> select * from teacher </select> <resultMap id="TeacherStudent1" type="Teacher"> <result property="id" column="id"/> <result property="name" column="name"/> <result property="cno" column="cno"/> <!-- column用户传递给子查询作为where条件 --> <collection property="students" column="cno" javaType="ArrayList" ofType="Student" select="getStudent"/> </resultMap> <select id="getStudent" resultType="Student"> select * from student where cno=#{cno} </select>
查询结果:
Teacher(id=1, name=chentao, cno=1, students=[Student(id=1, name=liulang, cno=1), Student(id=2, name=lixiaosheng, cno=1), Student(id=3, name=zhenghaiqing, cno=1)])
总结:
1.关联-association【多对一】
2.集合-collection【一对多】
3.javaType & ofType
1.javaType用来指定实体类中属性的类型
2.ofTyp用来指定映射到list或则集合中的pojo类型,泛型中的集合类型
注意:
-
保证SQL的可读性,尽量保证通俗易懂
-
注意一对多和多对一中,属性名和字段的问题
-
如果问题不好排查,可以使用日志,建立使用log4j
12.动态SQL
什么是动态SQL:根据不同的条件生成不同的SQL语句
所谓动态SQL:本质还是SQL语句,只是我们可以再SQL层面,去执行一个逻辑代码
1.搭建环境
开启驼峰命名:只应用于数据库映射java使用的驼峰命名
<settings> <!-- 开启驼峰命名映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
2.where
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
使用where标签如果你满足的第一个条件在where 后面添加了一个and,会被这个标签自动删除and然后正常执行,如果你没有过滤条件,那么where标签会将where字句删除
<where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where>
3.IF
BlogMapper.xml
<insert id="addBlog" parameterType="blog"> insert into blog values(#{id},#{title},#{author},#{createTime},#{view}); </insert> <select id="queryBlogIf" parameterType="map" resultType="blog"> select * from blog where 1=1 <if test="title != null"> and title=#{title} </if> <if test="author != null"> and author = #{author} </if> </select>
test:
public void queryBlogIf(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); map.put("title","Mybatis"); // map.put("author","ll"); List<Blog> blogs = mapper.queryBlogIf(map); for (Blog item : blogs) { System.out.println(item); } sqlSession.close(); }
4.choose(swtich)
<select id="queryBlogChoose" resultType="blog" parameterType="map"> select * from blog <where> <choose> <when test="title != null"> title=#{title} </when> <when test="author != null"> author=#{author} </when> <otherwise> view > 0 </otherwise> </choose> </where> </select>
作用:和java的swtich功能类似
-
只会满足其中之一,如果存在满足多个条件的话也只会满足第一个条件
5.Set
作用:set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号```xml
<update id="updateBlogSet" parameterType="map" > update blog <set> <if test="title != null"> title=#{title}, </if> <if test="author != null"> author=#{author}, </if> <if test="view != null"> view=#{view}, </if> </set> where id = '35a166859fb84884b1e18732a05516ff' </update>
6.foreach(几乎不用)
我们想查出数据库前两条数据
<select id="queryfreeBlog" resultType="Blog"> select * from Blog <where> <foreach collection="list" item="id" open="and (" close=")" separator="or"> id = #{id} </foreach> </where> </select>
open:循环开始之前拼接的内容;
close结:循环结束拼接的内容
separator:指定循环中的分隔符
collection:循环遍历的内容
item:遍历的单个变量,在标签体重来使用
@Test public void queryfreeBlog(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); ArrayList<String> str = new ArrayList<String>(); str.add("35a166859fb84884b1e18732a05516ff"); str.add("6d5cb2ff745c4f658eefd4b21add44f9"); map.put("list",str); List<Blog> blogs = mapper.queryfreeBlog(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); }
结果:
Blog{id='35a166859fb84884b1e18732a05516ff', title='这不是结局', author='小新', createTime=2021-07-29, view=998} Blog{id='6d5cb2ff745c4f658eefd4b21add44f9', title='java', author='刘星', createTime=2021-07-29, view=1005}
注意:
如果这里我们没有传递任何参数,就会自动删除where条件,将会查出所有信息
7.SQL片段
有的时候,我们可能会将一些功能的部分抽取出来,方便复用
<sql id="if-author-title"> <if test="title != null"> and title=#{title} </if> <if test="author != null"> and author = #{author} </if> </sql> <select id="queryBlogIf" parameterType="map" resultType="blog"> select * from blog <where> <include refid="if-author-title"/> </where> </select>
实现代码的复用
使用sql标签提取公共部分
在使用的地方用include标签引入SQL片段通过id引入
注意:
-
最好基于单表来定义SQL片段
-
不要存在where标签
-
最好只要一些IF判断就好了
13.缓存(了解)
1.什么是缓存?
-
存在内存中的临时数据
-
将用户数据查询到的数据放在缓存中,用户去查询数据就不用从磁盘上查询,从缓存中读取,从而提高效率,解决了高并发系统的性能问题
2.为什么要使用缓存?
-
减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据能使用缓存?
查询:连接数据库,耗内存
解决:一次查询的结果,给他暂存在一个可以直接读取的地方--》内存:缓存
我们再次查询相同数据的时候我们走缓存,就不用走数据库了。
14.Mybatis缓存
-
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大地提升查询效率
-
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启。(本地缓存)
-
二级缓存需要手动开启和配置,他是基于namespace级别的缓存
-
为了提高扩展性,mybatis定了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存
一级缓存
-
一级缓存也叫本地缓存
-
与数据库同义词会话期间查询到的数据会被放在本地缓存中
-
以后如果需要获取相同的数据的话,直接从缓存中拿,没有必要在去查数据库
实验步骤
1.开启日志
2.测试在一个Session中查询两次相同的记录
3.查看日志输出
@Test public void queryAllUser(){ SqlSession sqlSession = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.queryAllUser(6); System.out.println(users); System.out.println("============================================="); List<User> users1 = mapper.queryAllUser(6); System.out.println(users1); System.out.println(users==users1); sqlSession.close(); }
增删改会刷新原来的东西
缓存失效的情况:
-
增删改都有可能改变原来的数据,必定会刷新缓存
-
查询不同的Mapper.xml
-
手动清理缓存
-
查询不同的东西
小结:一级缓存默认是开启的,只在一次SqlSessionFactory中有效,也就是拿到链接到关闭连接区间有效。一级缓存也是无法关闭的。
一级缓存作用域就是sqlsession对象,存储的内容,查询后的结果集,在sqlSession对象中有Map结果,Map的值就是结果集的对象。
二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
-
工作机制:
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,这个会话对应的一级缓存就没有了;但是我们想要的是,会话关闭了,一级缓存中的数据会被保存到二级缓存中
-
新的会话查询信息,就可以从二级缓存中获取内容;
-
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:<cache/>
步骤:
1.开启缓存设置
<setting name="cacheEnabled" value="true"/>
2.要是用二级缓存需要在Mapper中开启
<!--在当前的xml中开启二级缓存--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
也可以自定义一些参数
3.测试
@Test public void queryAllUser(){ SqlSession sqlSession1 = MybatisUtil.getsqlSession(); SqlSession sqlSession2 = MybatisUtil.getsqlSession(); UserMapper mapper = sqlSession1.getMapper(UserMapper.class); List<User> users = mapper.queryAllUser(6); System.out.println(users); sqlSession1.close(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); List<User> users2 = mapper2.queryAllUser(6); System.out.println(users2); sqlSession2.close(); }
只进行了一次查询
小结:二级缓存只能在一个namespace内有效,也就是只在一个xml文件内。所有的数据都会先放在一级缓存中,只有当会话提交或关闭的时候转存到二级缓存
问题!:我们需要将实体类序列化
implements Serializable
查找顺序
自定义缓存--ehcache
ehcache是一种广发使用的开源java分布式缓存
Mybatis注意点
mybatis-config.xml:
MybatisUtil.java
UserMapper.xml