介绍
maven父项目-子项目
maven父项目将src目录删除
父项目 管理多个 子项目,父项目引入的依赖可以直接给子项目使用
父项目的完整坐标:groupId[组织名] + artifactId[项目名]
父项目pom.xml
<!-- <packaging>pom</packaging>表示父项目以多个子项目管理工程 -->
<!-- modules 指定管理了哪些子项目 -->
<groupId>com.hspedu</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>mybatis_quickstart</module>
</modules>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<!--
在父工程的 pom.xml 加入 build 配置
在 build 中配置resources,来防止我们资源导出失败的问题
含义是将 src/main/java目录和子目录 和 src/main/resources目录和子目录
的资源文件 xml 和 properties在build项目时,导出到对应的target目录下
-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
子项目pom.xml
<!--
parent 指定了该项目的父项目坐标
当前项目的 groupId 就是 com.hspedu
-->
<parent>
<groupId>com.hspedu</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>mybatis_quickstart</artifactId>
开发步骤
1、引入依赖:mybaits、mysql
2、核心:mybatis-config.xml,放在类路径下,指明xxMapper.xml文件的路径
3、编写xxMapper.xml,放在类路径下
手动commit()
mybatis-config.xml
xml需要按照标签顺序写,resource一般都是从类路径下找文件
可以引入外部文件(xx.properties)来设置数据源的值
<properties resource="jdbc.properties">
<property name="username" value="${外部文件的字段名}"/>
<property name="password" value="${外部文件的字段名}"/>
配置别名,不区分大小写,alias可以省略,默认别名就是类名,Car/CAR/car ...
parameterType可以简写
<typeAliases>
<typeAlias type="com.hspedu.entity.Car" alias="Car"/>
</typeAliases>
或者
<typeAliases>
//将这个包下的所有的类自动起别名
<package name="com.hspedu.entity"/>
</typeAliases>
日志
<!--配置MyBatis自带的日志输出(标准日志)-查看原生的sql-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
事务管理器
指定Mybatis具体使用什么方式去管理事务
<transactionManager type="xxx"/>大小写都可以
1、JDBC
mybatis框架自己管理事务,采用原生的JDBC代码去管理事务
conn.setAutoCommit(false);开启事务 = sqlSessionFactory.openSession();
conn.commit();手动提交事务 = sqlSession.commit();
2、MANAGED
事务管理交给其他容器来负责,例如:spring,找不到的话还是JDBC来管理事务
mapper标签
1、resource:从类路径下加载资源,"carMapper.xml"
2、url:绝对路径,"file///d:/carMapper.xml"
3、class:全限定接口名, "com.hspedu.mapper.CarMapper"
会在同级目录下com.hspedu.mapper找xml文件,如果一定要在resource目录下,建立目录com/hspedu/mapper,结构要和包名一样且名字要保持一致CarMapper接口对应CarMapper.xml,因为执行的时候java和resource都会放在target的根路径下
第四种跟第三种规则一样
<?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>
<typeAliases>
<typeAlias type="com.hspedu.entity.Monster" alias="Monster"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--配置数据源-->
<dataSource type="POOLED">
<!--
userSSL=true 表示安全连接
& 表示& 防止解析错误
useUnicode=true 防止编码错误
characterEncoding=UTF-8 防止中文乱码
-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>
<!--<mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>-->
<!--<mapper class="com.hspedu.mapper.MonsterAnnotation"/>-->
<package name="com.hspedu.mapper"/>
</mappers>
</configuration>
一个环境(数据库)对应一个SqlSessionFactory对象,default表示默认使用的环境
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream,"环境ID");
MonsterMapper.xml
#{aBC},#{ }里的参数是get方法去掉get后第一个字母小写,底层会找getABC()方法
namespace:指定该xml文件和哪个接口对应,不能简写 id:指定方法名 保证唯一不冲突
<?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.hspedu.mapper.MonsterMapper">
<insert id="addMonster" parameterType="com.hspedu.entity.Monster">
insert into `monster`
(`age`,`birthday`,`email`,`gender`,`name`,`salary`)
VALUES (#{age},#{birthday},#{email},#{gender},#{name},#{salary});
</insert>
<delete id="delMonster" parameterType="Integer">
DELETE FROM `monster` WHERE id = #{id}
</delete>
<update id="updateMonster" parameterType="Monster">
UPDATE `monster`
SET `age`=#{age} , `birthday`= #{birthday}, `email` = #{email},
`gender` = #{gender} , `name`= #{name}, `salary` = #{salary}
WHERE id = #{id}
</update>
<select id="getMonsterById" resultType="Monster">
SELECT * FROM `monster` WHERE id = #{id}
</select>
<select id="findAllMonster" resultType="Monster">
SELECT * FROM `monster`
</select>
</mapper>
JavaBean-entity-POJO
字段与数据库中的表对应,使用包装类,可以存放null,不会报错
MonsterMapper接口
方法的实现可以用注解或放在XXMapper.xml中
public interface MonsterMapper {
public void addMonster(Monster monster);
public void delMonster(Integer id);
public void updateMonster(Monster monster);
public Monster getMonsterById(Integer id);
public List<Monster> findAllMonster();
}
注意
配置文件名称和mapper接口名称是否要一致
一:当核心配置文件mapper标签下以resource形式指向依赖配置文件时,不需要
这样就可以加载到其相应的依赖配置文件通过namespace找到其相应的方法
二:如果mapper标签下以package包扫描形式时,需要。
原因如下:
1.包扫描形式时。实体类+Mapper接口通过动态代理调用方法
2.调用方法时会找其相应的映射配置文件
3.当多个mapper接口和mapper.xml同时存在,如果没有相同的名称,则动态代理就不能通过其一一对应的依赖配置文件创建其相应的实现方法
测试
mybatis-config.xml是全局配置文件,只能有一个
SqlSessionFactoryBuilder的作用就是生成工厂,生命周期最短,只需要加载一次,所以写成静态代码块
SqlSessionFactoryBuilder(mybatis-config.xml)-> SqlSessionFactory -> SqlSession
SqlSession底层是Executor(执行器),有两个重要的实现类:基本执行器、缓存执行器
MyBatisUtils
2、3写成工具类
使用ThreadLocal,全局的对象
提交sqlSession.commit();
关闭SqlSessionUtil.close(sqlSession);不能使用sqlSession.close();
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
static {
try {
//指定配置文件
String resource = "mybatis-config.xml";
//import org.apache.ibatis.io.Resources;
//获取配置文件 mybatis-config.xml 对应的inputStream
//加载文件时,默认到resources目录 对应到 运行后的工作目录classes
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//返回SqlSession对象-会话
public static SqlSession getSqlSession() {
SqlSession sqlSession = local.get();
if(sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
//绑定到当前线程上
local.set(sqlSession);
}
return sqlSession;
}
public static void close(SqlSession sqlSession) {
if(sqlSession != null) {
sqlSession.close();
//移除绑定关系
local.remove();
}
}
}
public class MonsterMapperTest {
private SqlSession sqlSession;
private MonsterMapper monsterMapper;
@Before
public void init() {
sqlSession = MyBatisUtils.getSqlSession();
//返回的是接口代理对象
monsterMapper = sqlSession.getMapper(MonsterMapper.class);
System.out.println(monsterMapper.getClass());
}
@Test
public void addMonster() {
for (int i = 0; i < 2; i++) {
Monster monster = new Monster();
monster.setAge(10 + i);
monster.setBirthday(new Date());
monster.setEmail("kate@qq.com");
monster.setGender(1);
monster.setName("大象精-" + i);
monster.setSalary(1000 + i * 10);
monsterMapper.addMonster(monster);
System.out.println("添加对象--" + monster);
System.out.println("添加到表中后, 自增长的id=" + monster.getId());
}
//如果是增删改, 需要提交事务
if(sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
System.out.println("保存成功...");
}
@Test
public void delMonster() {
monsterMapper.delMonster(2);
if(sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
System.out.println("删除成功...");
}
@Test
public void updateMonster() {
Monster monster = new Monster();
monster.setAge(50);
monster.setBirthday(new Date());
monster.setEmail("king3@qq.com");
monster.setGender(0);
monster.setName("老鼠精-01");
monster.setSalary(2000);
monster.setId(3);
monsterMapper.updateMonster(monster);
if(sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
System.out.println("修改成功...");
}
@Test
public void getMonsterById() {
Monster monster = monsterMapper.getMonsterById(3);
System.out.println("monster=" + monster);
if(sqlSession != null) {
sqlSession.close();
}
System.out.println("查询成功~");
}
@Test
public void findAllMonster() {
List<Monster> monsters = monsterMapper.findAllMonster();
for (Monster monster : monsters) {
System.out.println("monster-" + monster);
}
if(sqlSession != null) {
sqlSession.close();
}
System.out.println("查询成功~");
}
}
返回自增长id
//在MonsterMapper.xml中 //设置useGeneratedKeys="true" keyProperty="id"可以获得自增长的id //执行完插入语句,对象id一般都是null,现在可以获取到id值 System.out.println(monster.getId()); System.out.println("添加成功");
原生API调用
DefaultSqlSession底层方法
最终执行的还是XML配置文件中的方法
/*
statement 就是接口方法-完整声明
parameter 入参
*/
public int insert(String statement, Object parameter) {
return this.update(statement, parameter);
}
<insert id="addMonster" parameterType="Monster" useGeneratedKeys="true" keyProperty="id">
INSERT INTO `monster`
(`age`, `birthday`, `email`, `gender`, `name`, `salary`)
VALUES (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})
</insert>
public class MonsterMapperTest {
private SqlSession sqlSession;
@Before
public void init() {
sqlSession = MyBatisUtils.getSqlSession();
//sqlSession 返回的对象是 DefaultSqlSession
System.out.println(sqlSession.getClass());
}
@Test
public void addMonster() {
Monster monster = new Monster();
monster.setAge(100);
monster.setBirthday(new Date());
monster.setEmail("kate@qq.com");
monster.setGender(1);
monster.setName("大象精-100");
monster.setSalary(1000);
//最终找的还是xml中的方法名字
sqlSession.insert("com.hspedu.mapper.MonsterMapper.addMonster",monster);
/*
修改必须setid
monster.setId(3);
sqlSession.update("路径",monster);
* */
//如果是增删改, 需要提交事务
if(sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
System.out.println("保存成功...");
}
}
使用注解方式
public interface MonsterAnnotation {
@Insert("insert into `monster` (`age`,`birthday`,`email`,`gender`,`name`,`salary`) VALUES (#{age},#{birthday},#{email},#{gender},#{name},#{salary})")
public void addMonster(Monster monster);
@Delete("DELETE FROM `monster` WHERE id = #{id}")
public void delMonster(Integer id);
@Select("SELECT * FROM `monster` WHERE id = #{id}")
public Monster getMonsterById(Integer id);
@Select("SELECT * FROM `monster`")
@Results({
@Result(property="", column=""),
@Result(property="", column=""),
@Result(property="", column="")//数据库字段和Bean字段名不一致设置映射
})
public List<Monster> findAllMonster();
}
返回自增长id
keyProperty 和 keyColumn相同可以省略 keyColumn
xxMapper.xml分析
#{ }和${ }
#{ }:底层使用PreparedStatement,sql先编译再给?传值,?变成 ‘ 值 ’,存在单引号
${ }:底层使用Statement,sql先拼接再编译,存在sql注入风险
1、给sql语句传关键字(asc|desc)
2、sql语句字段拼接,字段名例如:字段+日期
3、批量删除,有两种写法in和or(动态sql)
4、模糊查询
<select>
//传入date
select * from monster_${date}
</select>
<delete>
//传入字符串 “1,2,3”
delete form monster where id in (${ids})
</delete>
<select>
//在jdbc中,'%?%'中的?不会被识别为占位符
//传入name
select * from `monster` where `name` like '%${name}%'
like concat('%',#{name},'%')
like "%"#{name}"%"使用较多
</select>
parameterType/resultType
查询语句如果返回的是List,resultType应该写List集合中的元素类型(全类名(可简写)),因为Mybatis的方法自动返回List集合,但不知道是什么类型
简单类型
在核心配置文件设置别名后,parameterType可以简写或省略
java.lang.Integer,有内置别名,Integer,底层自动调用xx.setxx类型()进行推断,可以省略
加上的话效率会高些,也可以#{name, javaType=String,jdbcType=VARCHAR}这样写
多个简单类型参数使用注解
#{ }里只能写arg或者param
Mybatis框架会自动创建一个Map集合
map.put("arg0", name); map.put("arg1",sex);
map.put("param1", name); map.put("param2",sex);
可以混合使用,可读性差,使用注解
加上注解后param仍然可以用或者#{name},#{sex}
public List<Monster> find(@Param("name") String name, @Param("sex") Character sex);
Map入参
public List<Monster> find(Map<String, Object> map);
<select id="find" parameterType="map" resultType="Monster">
select * from `monster` where `id` > #{id} and `salary` >= #{salary}
</select>
@Test
public void m3() {
Map<String, Object> map = new HashMap<>();
map.put("id",3);
map.put("salary",1000);
List<Monster> monsters = monsterMapper.find(map);
for (Monster monster : monsters) {
System.out.println(monster);
}
}
Map出参(没有对应的Bean)
public List<Map<String,Object>> find(Map<String, Object> map);
<select id="find" parameterType="map" resultType="map">
select * from `monster` where `id` > #{id} and `salary` >= #{salary}
</select>
@Test
public void m4() {
Map<String, Object> map = new HashMap<>();
map.put("id",3);
map.put("salary",1000);
List<Map<String,Object>> monsterlist = monsterMapper.find(map);
for (Map<String, Object> monsterMap : monsterlist) {
// 方式一
// Set<String> keys = monsterMap.keySet();
// for (String key : keys) {
// System.out.println(key+ " " + monsterMap.get(key));
// }
// System.out.println("===========");
// 方式二
for(Map.Entry<String,Object> entry : monsterMap.entrySet()) {
System.out.println(entry.getKey()+ " " + entry.getValue());
}
System.out.println("=========");
}
}
会将查询结果的id值作为整个大Map集合的key
@MapKey("id")
public Map<Integer,Map<String,Object>> findAll();
resultMap结果映射
select查询后,某些字段为null,是因为Bean的字段名和表的字段名不一致
第一种(常用)
property表示Bean字段名
column表示数据库字段名
type表示Bean类名
相同可以省略,主键使用id标签<id property="id" column="id"/>提高效率,其他都是<result>
第二种
在写sql语句的时候写别名,跟Bean的字段名保持一致
第三种
开启驼峰命名自动映射(配置settings)
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名规范
sql命名规范:全部小写,单词之间采用下划线分割
动态SQL
if
表单批量删除
多条件查询
不能使用&&,只能使用and
如果输入的agex >= 0就拼接
空值:name != null and name != ''
public List<Monster> findMonsterByAge(@Param(value = "agex") Integer age);
<select id="findMonsterByAge" resultType="Monster" parameterType="Integer">
select * from `monster` where 1 = 1
<if test="agex >= 0">
and `age` > #{agex}
</if>
<if test="xxx">
and xxx
</if>
</select>
where
所有条件为空时,where标签保证不会生成where子句
自动去除某些条件前面多余的and或or
public List<Monster> findMonsterByIdAndName(Monster monster);
<select id="findMonsterByIdAndName" parameterType="Monster" resultType="Monster">
select * from `monster`
<where>
<if test="id >= 0">
and `id` > #{id}
</if>
<if test="name != null and name != ''">
and `name` = #{name}
</if>
</where>
</select>
执行的时候会自动去掉语句中最前面的and或or
choose-when-otherwise
相当于if elseif elseif else
先根据name查,name为空根据id查,否则根据salary查
如果都为空,会执行最后一条语句
public List<Monster> findchoose(Map<String, Object> map);
<select id="findchoose" parameterType="map" resultType="Monster">
select * from `monster`
<choose>
<when test="name != null and name != ''">
where `name` = #{name}
</when>
<when test="id > 0">
where `id` > #{id}
</when>
<otherwise>
where `salary` > 100
</otherwise>
</choose>
</select>
foreach
常用于对集合进行遍历(尤其是在构建IN条件语句的时候)
collection:指定数组或集合
item:代表数组或集合中的元素
separator:循环之间的分隔符
open:foreach循环拼接的所有sql语句的最前面以什么开始
close:foreach循环拼接的所有sql语句的最后面以什么开始
public List<Monster> findforeach(Map<String, Object> map);
<select id="findforeach" parameterType="map" resultType="Monster">
select * from `monster`
<if test="ids != null and ids != ''">
<where>
id IN
<foreach collection="ids" item="idx" open="(" separator="," close=")">
#{idx}
</foreach>
</where>
</if>
</select>
@Test
public void m1() {
Map<String, Object> map = new HashMap<>();
map.put("ids", Arrays.asList(1,3,5));
List<Monster> monsters = monsterMapper.findforeach(map);
for (Monster monster : monsters) {
System.out.println(monster);
}
}
批量删除,参数是数组
int deleteByIds (@Param("ids") Long[] ids);
底层map存放的key是array或arg0,value是这个数组,collection只能写array或arg0
写注解就可以用collection=“ids”
批量插入
int insertBatch (@Param("cars") List<Car> cars);
trim
preifx:加前缀
suffix:加后缀
prefixOverrides:删除前缀
suffixOverrides:删除后缀
set
主要用在update语句中,用来生成set关键字,同时去掉最后多余的 ","
只更新提交的不为空的字段,如果提交的数据是空或者“ ”,那么这个字段将不更新数据
public void setMonster(Map<String, Object> map);
<update id="setMonster" parameterType="map">
update `monster`
<set>
<if test="age != null and age != ''">
`age` = #{age},
</if>
<if test="email != null and age != ''">
`email` = #{email},
</if>
</set>
where id = #{id}
</update>
@Test
public void m1() {
Map<String, Object> map = new HashMap<>();
map.put("age",2222);
map.put("email", "2222@22.com");
map.put("id",3);
monsterMapper.setMonster(map);
if(sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
}
映射关系
一对一
一个人对应一张身份证
配置xxMapper.xml的方式
方式一
PersonMapper
public interface PersonMapper {
//包括Person关联的IdenCard对象【级联查询】
public Person getPersonById(Integer id);
}
PersonMapper.xml
使用自定义的resultMap
<mapper namespace="com.hspedu.mapper.PersonMapper">
<!--
自定义resultMap
type="Person",最终返回的还是Person对象,是简写
property="xx" 对应表中的属性
column="xx" 对应查询结果的列名
-->
<resultMap id="PersonResultMap" type="Person">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--
association 一个复杂类型的关联
property="card" ,表示Person对象的card属性
javaType="IdenCard",表示card属性的类型
-->
<association property="card" javaType="IdenCard">
<result property="id" column="id"/>
<result property="card_sn" column="card_sn"/>
</association>
</resultMap>
<!--使用自定义的resultMap-->
<select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
select * from `person`,`idencard` where `person`.id = #{id} and `person`.card_id = `idencard`.id
</select>
</mapper>
方式二(常用)
多表联查分解成单表操作
步骤
第一张表
<mapper namespace="com.hspedu.mapper.IdenCardMapper">
<select id="getIdenCardById" parameterType="Integer" resultType="IdenCard">
select * from `idencard` where `id` = #{id}
</select>
</mapper>
第二张表
<mapper namespace="com.hspedu.mapper.PersonMapper">
<resultMap id="PersonResultMap" type="Person">
<id property="id" column="id" />
<result property="name" column="name"/>
<!--
select是另一张表的单表操作
column="card_id" 是 select * from `person` where `id` = #{id} 返回的字段名
column="card_id"作为select="com.hspedu.mapper.IdenCardMapper.getIdenCardById"的入参来执行
-->
<association property="card" column="card_id"
select="com.hspedu.mapper.IdenCardMapper.getIdenCardById" />
</resultMap>
<!---->
<select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
select * from `person` where `id` = #{id}
</select>
</mapper>
通过注解实现的方式
第一张表
public interface IdenCardMapperAnnotation {
@Select("select * from `idencard` where `id` = #{id}")
public IdenCard getIdenCardById(Integer id);
}
第二张表
public interface PersonMapperAnnotation {
/*
入参parameterType="Integer" 在形参已经体现
resultMap="PersonResultMap" 写成 @Results
*/
@Select("select * from `person` where `id` = #{id}")
@Results({
/*
<result property="id" column="id"/> 可以标记出作为ID的结果
<result property="name" column="name"/>等价于Result
* */
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "name"),
/*
<association property="card" column="card_id"
select="com.hspedu.mapper.IdenCardMapper.getIdenCardById" />
一对一用 @One
* */
@Result(property = "card", column = "card_id",
one = @One(select = "com.hspedu.mapper.IdenCardMapperAnnotation.getIdenCardById"))
})
public Person getPersonById(Integer id);
}
细节
多对一
多对一、一对多,谁在前,谁就是主表
多个学生对应一个班级
数据库表名分别是t_stu、t_clazz
注意:
班级类如果有学生类private List<Student> stus;
测试时会报错(栈溢出),toString方法反复调用
双向映射不要用toString,使用get方法
@Test
public void m1() {
User user = userMapper.getUserById(1);
System.out.println(user.getId() + " " + user.getName());
List<Pet> pets = user.getPets();
for (Pet pet : pets) {
System.out.println(pet.getId() + pet.getNickname());
}
}
方式一:一条sql语句(级连属性映射)
public interface StudentMapper {
Student selectById(Integer id);
}
把主表的每个字段指定一下,对象属性通过(对象名.属性名)来指定
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<resultMap id="stuResultMap" type="student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
</resultMap>
<select id="selectById" resultMap="stuResultMap">
select
s.sid,s.sname,s.cid,c.cname
from
t_stu s left join t_clazz c
on
s.cid=c.cid
where
s.sid = #{sid}
</select>
</mapper>
方式二:一条sql语句(association)
association:关联,一个Student对象关联一个Clazz对象
<association property="clazz" column="clazz">
property提供要映射Bean类的属名 javaType:设置别名可以简写
<resultMap id="stuResultMapAssociation" type="student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
//关联一个对象
<association property="clazz" javaType="clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
</association>
</resultMap>
方式三:两条sql语句,分布查询
优点:可复用、支持懒加载(用到的时候再执行查询语句)
public interface StudentMapper {
Student selectByIdStep1(Integer id);
}
type可以简写
public interface ClazzMapper {
Clazz selectByIdStep2(Integer id);
}
开启懒加载
<!--在association中设置的仅是局部设置-->
<association fetchType="lazy|eager">
//只输出学生的名字,只会执行一条语句
//student.getName();
一般在mybatis-config.xml核心配置文件中设置全局懒加载
实际开发,全局开启,不想要再关闭
<settings>
<!--开启全局延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
一对多
方式一:collection
public interface ClazzMapper {
Clazz selectByCollection(Integer id);
}
<resultMap id="clazzResultMapCollection" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<!--
property:集合变量名称
ofType:集合中元素的类型
-->
<collection property="stus" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
<select id="selectByCidCollection" resultMap="clazzResultMapCollection">
select
c.cid,c.cname,s.sid,s.sname
from
t_clazz c left join t_stu s
on
c.cid=s.cid
where
c.cid=#{cid}
</select>
方式二:分布查询
<resultMap id="clazzResultMapStep" type="clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus" ofType="student" select="com.powernode.mybatis.mapper.StuMapper.selectByCid" column="cid" >
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
<select id="selectByCidStep" resultMap="clazzResultMapStep">
select cid,cname from t_clazz where cid=#{cid}
</select>
缓存
通过减少IO的方式,来提高程序的运行效率
缓存只针对DQL语句,即select语句
一级缓存:将查询到的数据存放到SqlSession中
二级缓存:将查询到的数据存放到SqlSessionFactory中
其他第三方的缓存(EhCache)
一级缓存
作用域:sqlSession会话级别,在一次会话有效
默认是开启的,不需要任何配置
两个SqlSession其实是一样的,因为在openSession()时,使用了ThreadLocal
两条同样的select语句,只会执行一次sql语句
(只要使用同一个SqlSession对象执行同一条sql语句,就会走缓存)
SqlSession会话不关闭,就一直存在
Local Cache中有数据,直接返回,否则操作数据库将数据存放在Local Cache
使用 hashmap 存储数据
hashmap 的 key是:(组合值)hashCode+查询的SqlId+编写的 sql 查询语句+参数
value(arraylist):是查询后得到的数据,可能是多个数据
失效分析
1、关闭SqlSession会话后,再次查询需要重新初始化SqlSession,会到数据库查询
2、执行SqlSession.clearCache()3、执行了增删改语句,不管操作哪张表
当查询中的参数改变 或 两次查询之间有任何一次增删改操作 或 手动清除了缓存,一级缓存都会失效
二级缓存
CachingExecutor先获取二级缓存(自带/第三方库),看是否有数据
作用域范围:全局范围SqlSessionFactory
两次查询之间出现了增删改操作,二级缓存就会失效
使⽤⼆级缓存需要具备以下⼏个条件:
1、在mybatis-config.xml配置中开启二级缓存,默认是开启的
<setting name="cacheEnabled" value="true">
2、在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加配置
<cache />
3、Bean类实现序列化接口
4、SqlSession对象关闭或提交后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。
此时⼆级缓存才可⽤。
相关属性
在xxMapper.xml文件中配置
1 、eviction=“FIFO”:
缓存回收策略:
2 、flushInterval:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔(除非执行增删改),缓存仅仅在调用语句时刷新,刷新完失效
3 、size:引用数目,正整数
默认1024,代表缓存最多可以存储多少个对象,太大容易导致内存溢出
4 、readOnly:只读,true/false,默认是false
查询id=1的对象(发出sql语句),关闭sqlSession,获取sqlSession,再次查询id=1的对象,不会发出sql语句,而是从二级缓存获取数据,第一次命中率0,第二次命中率0.5,如果再次查询id=1的对象,命中率2/3
禁用二级缓存
1、mybatis-config.xml相当于总开关,管理很多映射器文件。不配置二级缓存,可以设置false
<setting name="cacheEnabled" value="false"/>
2、xxMapper.xml管理很多的方法实现。不配置二级缓存,可以注释掉<cache/>
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
3、在xxMapper.xml实现的方法中加上 useCache= false
<select id="getPetById" parameterType="Integer" resultType="Pet" useCache="false">
select * from mybatis_pet where id = #{id}
</select>
刷新二级缓存
细节
缓存执行顺序:二级 -> 一级 -> 数据库
当关闭会话一级缓存数据会没有,会把一级缓存的数据放在二级缓存
二级缓存的数据是一级缓存关闭后才有的,不可能出现重复的数据
二级缓存查询到才有命中率,在一个会话中查询4次id=1,命中率一直是0
分页PageHepler
获取数据不难,难得是获取分页相关的数据
1、引入依赖
2、在mybatis-config.xml配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
不用写limit,只需要写sql,执行前开启分页功能
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectAll" resultType="Car">
select * from t_car
</select>
</mapper>
@Test
public void test(){
SqlSession session = SqlSessionUtil.openSession();
CarMapper mapper = session.getMapper(CarMapper.class);
//查询第二页,前三条记录,在执行DQL语句之前,开启分页功能
PageHelper.startPage(2,3);
List<Car> cars = mapper.selectAll();
//输出cars会输出第二页的前三条记录
//pageInfo查看分页信息
//num代表导航的卡片的数量
PageInfo<Car> pageInfo = new PageInfo<>(cars, num);
System.out.println(pageInfo);
session.close();
}
Mybatis-plus
@TableId
指定表主键标识
前提确保数据库的主键也是自增
@Data
public class Furn {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String maker;
}
发送数据时,可以省略id
@TableName
类名和表名不一致,一致可以省略
-
@TableName(value = "monster")
-
public class Mon {}