Mybatis Note [vaynexiao]

HelloWorld

  • pom

(依赖+资源过滤问题)

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
  • 表设计
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user` (`id`, `name`, `age`) values('1','林更新','18');
  • 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>
    
    <typeAliases>
        <package name="com.kuang.pojo"/>  <!-- 默认首字母小写的类名 -->
        <typeAlias type="com.kuang.pojo.User" alias="u"/> 
    </typeAliases>
    
    <!-- environments 配置多个environment,供切换 -->
    <environments default="development"> <!--default指定一个默认环境的id-->
        <environment id="development">
            <!-- type:处理事务方式, 大多数情况使用jdbc,另一种 managed:将事物交给其他组件托管,如spring-->
            <transactionManager type="JDBC" />
            <!-- 配置数据库连接类型  常见的有:POOLED、UNPOOLED、JDNI -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <!-- useSSL true当文本不能全部显示时,后面用“…”来表示     false文本将自动换行
					这里的&报错,是因为IDEA将“&”当成了特殊符号。改为“&amp;”  
					url后跟问号,跟参数进一步设置
					?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8 -->
                <property name="url" value="jdbc:mysql://localhost:3306/cpt" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <!-- 使用相对于类路径的资源引用(推荐) -->
        <mapper resource="com/baidu/xxx/UserMapper.xml"/>
        <!-- 注册一个绝对路径的的Mapper.xml文件(不推荐) -->
        <mapper url="file:///E:/UserMapper.xml"/>
        <!-- 使用映射器接口实现类的完全限定类名,需要xml配置文件名称和接口名称一致,且同路径下 -->
        <mapper class="com.baidu.xxx.UserMapper"/>
        <!-- 将包内的映射器接口实现全部注册,需要配置文件名称和接口名称一致,且同路径下 -->
        <package name="com.baidu.xxx"/>
    </mappers>
    
</configuration>

  • Entity包中User.java
public  class User {
    
    
    private  int  id;
    private String name;
    private  int  age;
    //toString、全参构造、set、get、toString 此处全部省略。。。实际开发必须有,不然代码报错
}
  • MybatisUtil.java
public class MyBatisUtil {
    
    
    public static SqlSessionFactory getSqlSessionFactory() throws IOException {
    
    
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);//使用类加载器加载mybatis的配置文件
        //构建sqlSession的工厂 build方法还可以追一个参数,指定环境environment标签的id
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        return sqlSessionFactory;
    }
    public static SqlSession getSqlSession() throws IOException{
    
    
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        return sqlSessionFactory.openSession();//若是设置为opsenSession(true)默认自动提交事务,就不需要手动commit 
    }
}
  • UserMapper.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.baidu.cpt.mapper.UserMapper">
<!-- namespace为mapper指定唯一的id,通常设置成包名+sql映射的文件名-->
<!-- 这里namespace不建议写xml文件全名,这种方式已过时,建议写接口全名,使用面向接口的优秀方式 -->

        <!--  parameterType指明查询时使用的参数类型。如果参数只有1个则用简单类型,参数为多个时可用Map集合或实体对象,
        占位符必须用对象的属性名;
        resultType表示查询结果将要封装成的实体类,也可以为对象类型、Map类型、基本类型 -->
    <select id="selectOneUser" resultType="user" parameterType="int">
        select * from user where id = #{id}
    </select>
         
    <!--	 useGeneratedKeys  设置true,自动封装表的下一个自增值到对象中对应的属性
       		 keyProperty  指定对象的哪个属性 对应的是表的主键 
			特别注意的是表设计没有将id设置为自增,这里取不到自增值,会给一个id的默认值0,连续插入2次id为0就会主见重复报错
			如果传的参数User对象只构造了name和age,sql缺少id会报错
			keyColumn="id" 指对应的表字段 -->
    <insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
        insert into user(id, name, age) values(#{id}, #{name}, #{age})
    </insert>
    
    <insert id="insertUser2" parameterType="user" > 
        <selectKey keyProperty="id" order="BEFORE" resultType="int">  <!-- before在sql执行之前拿到,然后供sql使用 -->
            select CEILING(RAND()*10000)
        </selectKey>
        insert into user(id, name, age) values(#{id}, #{name}, #{age})
    </insert>
         
    <delete id="deleteUser">
        delete from user where id=#{id}
    </delete>
         
    <update id="updateUser" parameterType="user" >
        update user set nam e= #{name},age = #{age} where id = #{id}
    </update>
         
    <select id="selectAllUser" resultType="user">
        select * from user
    </select>

</mapper>
  • Test.java
public class Test {
    
    
    public static void main(String[] args)  throws Exception{
    
    

        SqlSession session = MyBatisUtil.getSqlSession();//至此得到一个session
        //Unhandled Exception:java.io.IOException  是因为少了throws Exception或者try/catch
        //org.apache.ibatis.binding.BindingException: Type interface xxx is not known to the MapperRegistry
        //上面的错误时因为namespace和接口名不相同,必须改为相同
        String namespace = "com.baidu.cpt.mapper.UserMapper.";//注意后面有一个点,为了和后面的mapper文件中的id连接

        String statement4 = namespace+"selectOneUser";
        User user = session.selectOne(statement4,1);
        //selectOne sql中需要一个参数就传一个,这里查出来几个字段,都会按照实体类构造方法按顺序进行赋值
        System.out.println("selectOne:"+user);
 
        String statement1 = namespace+"insertUser";//映射sql的标识字符串
        int insert = session.insert(statement1,new User(10,"张三",18)); 
        //insert 这里为什么插入后id并不是指定的10,因为该namespace中sql语句中没有插入id字段,而建表时主键id是自动增长的。
        session.commit(); //默认增删改 必须手动提交提交事务
        System.out.println("insert:"+insert);

        String statement2 = namespace+"deleteUser";
        int delete = session.delete(statement2,2);
        session.commit(); //默认增删改 必须手动提交提交事务
        System.out.println("delete:"+delete);

        String statement3 = namespace+"updateUser";
        int update = session.update(statement3,new User(1,"林更新",18));
        session.commit(); //默认增删改 必须手动提交提交事务
        System.out.println("update:"+update);

        String statement5 = namespace+"selectAllUser";
        List<User> list = session.selectList(statement5);
        System.out.println("selectList:"+list);
 
        session.close();//必须释放session
    }
}

三种helloworld方式区别

1,xml

<!-- mybatis-config.xml 中注册:UserMapper.xml,使用命名空间来找到 select 标签 -->
<mappers>
	<package name="com.baidu.cpt.mapper"/>
</mappers>

2,xml + interface(推荐,且最流行)

// 1,xml不变
    
// 2,增加接口
public interface UserMapper {
    
    
    public List<User> selectAllUser();  // 方法名必须和mapper文件的select标签id一致,mapper代理要求名字保持一致
}

// 3,测试方法和纯xml方式也不同,使用反射,根据接口类, 而不是namespace
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> lists = userMapper.selectAllUser();
for (User u : lists ) {
    
    
	System.out.println(u);
}

// 4,不再是注册xml,而是注册接口
<mappers>
    <package name="com.baidu.cpt.mapper"/>
</mappers>

3,interface + anotation

只需要interface,不再需要userMapper.xml,注解将sql写在方法上,当有注解时,默认使用方式三,但只适用简单sql
    
注意要同名,因为如下:
	本质:反射机制实现
	底层:动态代理!
    
// 1,新建一个接口文件,调用的方法不再是select标签,而是注解中sql
public interface UserMapperNew {
    
    

    @Select (  "select * from user"  )
    public List<User> selectAllUserNew() throws Exception;
    
    @Insert(  "insert into user(name, age) values(#{name}, #{age})"  )
    public int insertUser(User user) throws Exception;

    @Delete(  "delete from user where id=#{id}"  )
    public int deleteUser(int id) throws Exception;

    @Update(  "update user set name=#{name},age=#{age} where  id=#{id}"  )
    public int updateUser (User user,int id) throws Exception;

    @Select(  "select * from user where id = #{id}"  )
    public User selectOneUser(int id) throws Exception;
    
}
// 2,测试方法,调接口的方法,方法名无需和select标签一致,因为使用的该方法的注解中的sql,与userMapper.xml文件无关。
UserMapperNew userMapper = session.getMapper(UserMapperNew.class);
List<User> lists = userMapper.selectAllUserNew();
for (User u : lists ) {
    
    
	System.out.println(u);
}

mybatis是怎么找到mapper中的sql

// 使用类加载器加载mybatis的配置文件
InputStream is = Test1.class.getClassLoader().getResourceAsStream(“conf.xml”);

// 构建sqlSession的工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

// 通过工厂类来得到一个sqlSession,
SqlSession sqlSession = sqlSessionFactory.openSession();

String statement = “com.sl.mapper.ProductMapper.insertProduct”;
int count = session.insert(statement, product);
纯xml方式是根据namespace+id找到对应的sql来执行

ProductMapper productMapper = session.getMapper(ProductMapper.class);
List listProduct = productMapper.selectAllProduct();
xml+接口方式,是通过动态代理找到mapper中的id来执行sql

配置文件解析

标签有严格的顺序

configration
    properties 
    settings
    typeAliases
    typeHandlers类型处理器
    objectFactory对象工厂, objectWrapperFactory, reflectorFactory
    plugins
    environments
	    environment环境子属性对象
	    transactionManager事务管理
	    dataSource数据源 
    databaseIdProvider
    mappers
configration

引入外部配置文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=123456
<configuration>
    <properties resource="db.properties"/>
    <!-- properties 中可以使用子属性property自定义属性值,但优先会使用外部文件 -->
    <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>
</configuration>

setting

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

一、二级缓存

执行了一次查询后,向数据库发送了一条sql,再次查询时发现还是只发送了一条sql,
那是因为上一条sql查到的数据放入了缓存中,第二次直接取出来。加快了效率减小了系统负担。
一级缓存(本地缓存),sqlSession级别的缓存,一级缓存是默认一直开启的.
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据(参数与上一次相同,不然得到结果不同,自然缓存中不存在,无法得到),直接从缓存中取,没必要再去查询数据库
一级缓存的几种失效情况
    1. sqlSession更换,不是同一个session都不存在缓存的现象
    2. sqlSession相同,查询条件发生改变
    3. sqlSession相同,但两次查询之间执行了增删改操作,哪怕操作的条件不同,因为增删改有可能对原来数据有影响
    4. sqlSession相同,手动清除了一级缓存session.clearCache(),会进入二级缓存
    
二级缓存(全局缓存):
1,参与代码执行过程的实体类需要实现序列化接口,包括entity关联的entity类   implements Serializable)
基于namespace,namespace级别的缓存。
2,会话查到的数据默认存在一级缓存中,但如果关闭的话就会存入二级缓存中,不关闭就只存在一级缓存中,默认就是开启一级缓存。
<settings>
    <setting name="cacheEnabled" value="true" />
</settings> 
还需要在 Mapper 的xml 配置文件中加入 <cache/>标签,当然可以自定义一些参数
<cache readOnly="true" size="500" flushInterval="120000" eviction="LRU" />
size : 缓存存放多少数据。最大512
flushInterval : 缓存刷新间隔时间(毫秒时间),默认不清空
readOnly : 缓存是否只读;
eviction : 缓存回收策略
    LRU: 最近最少使用,移除最长时间不被使用的对象,默认策略
    FIFO: 先进先出,按对象进入缓存的顺序来移除他们
    SOFT: 软引用,移除基于垃圾回收器状态和软引用规则的对象
    WEAK: 弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
        
     true : MyBatis 认为所有从缓存中获取数据的操作都是只读。不会修改数据。MyBatis 为了加快获取速度,直接就会将数据在缓存中的引用交给用户。
     false : 非只读。MyBatis 会识别到数据可能会被修改,用户获得的数据是MyBatis 利用序列化和反序列化克隆的新数据交给用户(建议实体类实现序列化接口)。
     
SqlSession session1 = sf.openSession();
SqlSession session2 = sf.openSession();
UserinfoMappe mapper1 = session1.getMapper(UserinfoMapper.class);
UserinfoMappe mapper2 = session2.getMapper(UserinfoMapper.class);
Userinfo userinfo1 = mapper1.selectOne(101);
//会话关闭时,缓存会保存到二级缓存中,不关闭就进不去二级缓存中,就实现不了取数据
session1.close();
//从二级缓存中获取数据,不会从新发送新的 SQL 语句
Userinfo userinfo2 = mapper2.selectOne(101);
session2.close();

深入讲解:https://www.bilibili.com/video/BV1oJ411s7eS?from=search&seid=12127812127908391645

在这里插入图片描述

鉴别器

将查出来的数据,按照条件进行数据分类,第一类封装为A,第二类封装为B
比如:查出来sex是女生就返回封装类型A,查出来sex是男生返回封装类型B

<mapper namespace="com.xxx.entity.mapper.EmpMapper">

    <resultMap id="empMap" type="com.xxx.entity.Emp">
        <id property="id" column="id" />
        <result property="empName" column="emp_name" />
        <result property="sex" column="sex" />
        <association property="empCard" column="id"
            select="com.xxx.entity.mapper.EmpCardMapper.getEmpCardByEmpId" />
        <collection property="projectList" column="id"
            select="com.xxx.entity.mapper.ProjectMapper.findProjectByEmpId" />
        <discriminator javaType="int" column="sex">
            <!-- 这里设置为简单类型的话就不写resultMap,写resultType就可以 -->
            <case value="1" resultMap="maleEmpMap" /> 
            <case value="2" resultMap="femaleEmpMap" />
        </discriminator>
    </resultMap>

    <select id="getEmp" parameterType="int" resultMap="empMap">
        select id, emp_name as empName, sex from emp where id > #{id}
    </select>
     
    <resultMap id="maleEmpMap" type="com.xxx.pojo.MaleEmp" extends="empMap">
        <collection property="prostateList" select="com.xxx.mapper.MaleEmpMapper.findUserList" column="id" />
    </resultMap>
     
    <resultMap id="femaleEmpMap" type="com.xxx.pojo.FemaleEmp" extends="empMap">
        <collection property="uterusList" select="com.xxx.mapper.FemaleEmpMapper.findUserList" column="id" />
    </resultMap>
    
</mapper>

CDATA[转义字符]

将转义字符放在  <![CDATA[转义字符]]>  中
写的sql中有一些特殊的字符的话,在解析xml文件的时候会被转义,但我们不希望他被转义,使用XML语法<![CDATA[ ]]>

<select id="allUserInfo" parameterType="java.util.HashMap" resultMap="userInfo1">  
    <![CDATA[  
        SELECT *  WHERE 1=1  AND  newsday > #{startTime} AND newsday <= #{endTime}
	]]>   
</select> 

但是有个问题那就是 <if test=""></if>   <where></where>  <choose></choose>  <trim></trim> 等这些标签都不会被解析,
所以我们只把有特殊字符的语句放在 <![CDATA[   ]]>  尽量缩小 <![CDATA[  ]]> 的范围
SELECT state FROM emp WHERE state <![CDATA[ <> ]]>'effect'

调用存储过程

mysql
call pro_create_aaa( #{id}, #{age} )
{call pro_create_aaa( #{id}, #{age} )}

sqlserver
{call  pro_create_aaa   #{project_id},  #{cut_off_time}}

输入参数out,输入参数in
什么时候使用,jdbcType=NUMERIC
statementType="CALLABLE"
<insert id="addUser" parameterType="user" statementType="CALLABLE">
        {call insert_user(
        #{id, mode=OUT, jdbcType=INTEGER},
        #{name, mode=IN,jdbcType=NUMERIC},
        #{age, mode=IN,jdbcType=NUMERIC})}
</insert>

.trim() .toString()

select * from user where 1=1 
<where> 1=1
	<if test="year_id != null and year_id !='' ">
		and renzhu.year_id = #{year_id}
	</if>
	<if test="pcmc_name != null and deptName.trim() == '信息服务部'.toString() ">
		and p2.username like  '%${name}%'
	</if>
</where>

#$区别

sql注入
    原来:SELECT * FROM user WHERE username LIKE '%${value}%'
    传的参数:List<User> list=userMapper.findUserByName("' or '1'='1");
    实际sqlSELECT * FROM student WHERE stu_name LIKE '%' or '1'='1%'
    '1'='1%'相当于查询全部:SELECT * FROM student

在JDBC中,主要使用的是两种语句,
一种是支持参数化和预编译的PrepareStatement,能够支持原生的Sql,也支持设置占位符的方式,参数化输入的参数,防止Sql注入,
一种是支持原生Sql的Statement,有Sql注入的风险。

	• #{value} 这种取值是编译好SQL语句再取值,是安全的;
		在预处理时,会把参数部分用一个占位符 ? 替代,其中 value 表示接受输入参数的名称。
	• ${
   
   value} 这种是取值以后再去编译SQL语句,是非安全的,
		表示使用拼接字符串,将接受到参数的内容不加任何修饰符拼接在 SQL 中,将引起 SQL 注入问题。
		
1, #自动加单引号,$原样输出
SELECT * FROM SYS_PUB_ORGANIZATION  WHERE ORG_NAME LIKE  #{orgname}
--使用#解析为:SELECT * FROM SYS_PUB_ORGANIZATION  WHERE ORG_NAME = '长亮科技'
SELECT * FROM SYS_PUB_ORGANIZATION  WHERE ORG_NAME LIKE '%${orgname}%'
--使用$解析为:SELECT * FROM SYS_PUB_ORGANIZATION  WHERE ORG_NAME LIKE 长亮科技

一般能用 # 就不要使用$,若一定使用${xxx},需要手工做好过滤工作,来防止sql注入
$方式一般用于传入不需要加双引号的sql片段,比如数据库对象和表名

one to one

association javaType 【JavaType用来指定实体类中属性的类型】

学生属性有一个tid,表示他的老师,老师又是一个对象,这种称之为一对一

CREATE TABLE `teacher`(
`id` int(10) Not null,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`,`name`) VALUES (1,'秦老师');

CREATE TABLE `student`(
`id` int(10) Not null,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid`(`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO student(`id`,`name`,`tid`) VALUES (1,'小明',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (2,'小红',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (3,'小张',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (4,'小李',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (5,'小王',1);

方式1:按照查询嵌套处理

此方式查询次数较多,效率低

<!--  思路:
    1、查询所有的学生信息
    2、根据查询出来的学生的id的tid,寻找对应的老师! -子查询   -->

<select id="getStudent" resultMap="StudentTeacher">
	select * from student
</select>

<resultMap id="StudentTeacher" type="com.rui.pojo.Student">
    <!--复杂的属性,我们需要单独处理  对象:association  集合:collection-->
    <!--此处是学生关联了一个老师对象 association javaType select -->
    <association property="teacher" column="tid" javaType="com.rui.pojo.Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="com.rui.pojo.Teacher">
    select * from teacher where id = #{id}
</select>

方式2:按照结果嵌套处理:连表查询

<!--按照结果嵌套处理
与上一种方式差别是:sql改为关联查询,少一个select标签-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname,t.id tid
    from student s,teacher t
    where s.tid=t.id;
</select>

<resultMap id="StudentTeacher2" type="com.rui.pojo.Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="com.rui.pojo.Teacher">
        <result property="id" column="tid"></result>
        <result property="name" column="tname"></result>
    </association>
</resultMap>

one to many

collection ofType 【ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型】

比如:一个老师拥有多个学生!

对于老师而言,就是一对多的关系!

@Data
public class Teacher {
    
    
    private int id;
    private String name;
    //一个老师拥有多个学生
    private List<Student> students;
}

@Data
public class Student {
    
    
    private int id;
    private String name;
    private int tid;
}

方式1:按照结果嵌套处理

<!--按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
    select s.id sid,s.name sname,t.name tname,t.id tid
    from student s,teacher t
    where s.tid=t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="com.rui.pojo.Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--复杂的属性,我们需要单独处理  对象:association  集合:collection
        javaType="" 指定属性的类型
        集合中的泛型信息,我们使用ofType获取
    -->
    <collection property="students" ofType="com.rui.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

方式2:按照查询嵌套处理

collection javaType ofType

<select id="getTeacher2" resultMap="TeacherStudent2">
    select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="com.rui.pojo.Teacher">
    <collection property="students" javaType="ArrayList" ofType="com.rui.pojo.User" select="getStuById" column="id"/>
</resultMap>

<select id="getStudentByTeacherId" resultType="com.rui.pojo.Student">
    select * from mybatis.student where tid = #{tid}
</select>

分页

方式1:使用Limit分页,给sql传入startIndex, pageSize

List<User> getUserByLimit(Map<String,Integer> map);
<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex},#{pageSize}
</select>

方式2:RowBounds分页
由于 java 允许的最大整数为 2147483647,所以 limit 能使用的最大整数也是 2147483647,一次性取出大量数据可能引起内存溢出,所以在大数据查询场合慎重使用
RowBounds并不是一次性取出全部数据,因为 底层是对 JDBC 的封装,在 JDBC 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,为了避免溢出问题

List<User> getUserByRowBounds();
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
    select * from mybatis.user
</select>
@Test
public void getUserByRowBounds(){
    
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    //RowBounds实现
    RowBounds rowBounds = new RowBounds(1, 2);
    //通过java代码层面实现分页
    List<User> userList = sqlSession.selectList("com.rui.dao.UserMapper.getUser",null,rowBounds);
    for (User user : userList) {
    
    
        System.out.println(user);
    }
    sqlSession.close();
}

方式3:pagehelper

<!--pagehelper-->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.0.0</version>
</dependency>

<!-- pagehelper的依赖包:jsqlparser -->
<dependency>
  <groupId>com.github.jsqlparser</groupId>
  <artifactId>jsqlparser</artifactId>
  <version>0.9.5</version>
</dependency>

在mybatis核心配置xml中

<configuration>
    <plugins>
        <!-- 配置mybatis分页插件 -->
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 4.0.0以后版本可以不设置该参数 -->
            <property name="dialect" value="mysql"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
            <!-- 和startPage中的pageNum效果一样-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
            <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
            <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
            <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
            <property name="reasonable" value="false"/>
            <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
            <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
            <!-- 不理解该含义的前提下,不要随便复制该配置 -->
            <property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/>
            <!-- 支持通过Mapper接口参数来传递分页参数 -->
            <property name="supportMethodsArguments" value="false"/>
            <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
            <property name="returnPageInfo" value="none"/>
        </plugin>
    </plugins>
</configuration>
@Test
void testselectall() {
    
    
        //1.使用PageHelper类设置起始页和每页显示的条数
        int pageNum=5;//当前页码   从网页中可以获取
        int pageSize=3;//pageSize:自定义
        PageHelper.startPage(pageNum,pageSize);
        //2调用查询所有的方法
        List<User> list=userMapper.selectall();
        for (User user : list) {
    
    
            System.out.println(user);
        }
        //3.把查询的结果封装到Pageinfo中
        PageInfo<User> pageinfo=new PageInfo<>(list,4);
        System.out.println("上一页:"+pageinfo.getPrePage());
        System.out.println("当前页:"+pageinfo.getPageNum());
        System.out.println("下一页:"+pageinfo.getNextPage());
        System.out.println("总页数:"+pageinfo.getPages());
        int[] navigatepageNums = pageinfo.getNavigatepageNums();
        for (int i : navigatepageNums) {
    
    
            System.out.print(i+"\t");
        }
    }

日志

方式一:mybatis内置实现

<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

方式二:Log4j(推荐)

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

log4j.properties

# 主要配置
log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.apache=INFO
 
### 解释 ###
## log4j.rootLogger ##
# 作用:控制日志输出的级别、输出的位置。
# 参数:级别、输出的位置
# 级别: DEBUG < INFO < WARN < ERROR (除了这些,还有其他级别)
# 级别作用:以选DEBUG级别为例,大于DEBUG级别的信息(INFO/WARN/ERROR)也会输出
# 选用DEBUG级别的原因:是跟Mybatis的源码有关(你会发现最低的级别是DEBUG)。
# 查看源码1:可以将Mybatis的源码文件夹直接导入到项目中的Mybatis的jar包引用中。具体操作:右击项目--》
properties--》Java Build Path --》选择顶部的Libraries页面 --》点击展开mybatis.jar 
--》选择Source attachment --》点击右边的Edit --》External location --》 External File... 
-》选择Mybatis源码文件夹 --》OK --》Apply
# 查看源码2:项目中的Referenced Libraries --》 mybatis.jar 
--》org.apache.ibatis.logging.jdbc 里面的class文件
 
## log4j.appender.Console ## 
# 作用:真正控制,日志输出到什么地方,取决于参数用了什么类
# log4j.appender.名称  名称可以自定义,而 log4j.rootLogger中的位置就要写这个名称
 
## log4j.appender.Console.layout ##
# 作用:以什么布局方式输出日志,这里是自定义的布局
 
## log4j.appender.Console.layout.ConversionPattern ##
# 作用:就是你自定义的布局
# %d 是产生日志的时间。 %t 是产生这个日志所处的线程的名称。
# %p 是输出日志的级别,%-5p 5,就是输出5位字符,不足补空格 -,补的空格在右边
# %c 是输出的这个日志所在的类的全名。  %m 是你附加的信息。  %n 是换行
 
## log4j.logger.org.apache 对 org.apache ##
# 作用:的输出级别进行设置(也可以换成其他包),**此处**的会覆盖掉log4j.rootLogger设置的级别。就是选择我们需要看的信息。

生命周期和作用域


生命周期,和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。

SqlSessionFactoryBuilder

一旦创建了SqlSessionFactory,就不再需要它了
局部变量
mybatis运行流程SqlSessionFactory:

可以想象为:数据库连接池
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
因此SqlSessionFactory的最佳作用域是应用作用域。
最简单的就是使用 单例模式 或者静态单例模式
SqlSession

连接到连接池的一个请求!

SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

用完之后需要赶紧关闭,否则会占用资源

SqlSessionFactory

这里的每一个Mapper,就代表一个具体的业务!

传参数几种方式

此处针对的都是传递多个参数的情况,当传递单个参数时,传的参数名和mapper.xml中接收参数名都只有一个,
底层会直接视为同一个,进行处理。
    
====================传参方式1:将多个参数封装到一个map对象中,然后通过key取value==================
public interface UserDao {
    
    
    public List<User> selectUser(HashMap map) throws Exception;
}   
public void selectUser() throws Exception {
    
    
	SqlSession session = MybatisUtils.getSqlSession();   
	UserDao mapper = session.getMapper(UserDao.class);
	HashMap<String,String> map = new HashMap<String, String>();
	map.put("abc","1");
	List<User> list = mapper.selectUser(map);
	...
}
<select id="selectUser" parameterType="map"  resultType="user">
	select * from user where id = #{
    
    abc}
</select>              
        
=====================传参方式2:从接口获取的参数多个,通过下标索引index取值==================
public interface UserDao {
    
    
    public List<User> selectUser(String id, String name) throws Exception;
}
<select id="selectUser" parameterType="user"  resultType="user">
	select * from user where id = #{
    
    0}
</select>
        
        
=====================传参方式3:注解来告诉xml取对应名字的参数======
public interface UserDao {
    
    
    public List<User> selectUser(@Param("id") String id, @Param("name") String name) throws Exception;
}
<select id="selectUser" parameterType="map"  resultType="user">
	select * from user where id = #{
    
    id} and name = #{
    
    name}
</select>
  
        
=================传参方式4:实体类,比如参数封装为User,通过user的对应属性获取值==================
public interface UserDao {
    
    
    public List<User> selectUser(User u) throws Exception;
}
<select id="selectUser" parameterType="user"  resultType="user">
	select * from user where id = #{
    
    id} and name = #{
    
    name}
</select>
    
=================传参方式5:匿名参数 按顺序传递值 arg0等同于param1 arg1等同于param2================
List<User> selectUser(int age, String name)
<select id="selectUser" resultType="user" parameterType="user" databaseId="mysql">
    select * from user where 1=1
    <if test="arg0 != null">
        and age = #{
    
     param1}
	</if>
    <if test="arg1 != null">
        and name = #{
    
    param2 }
	</if>
</select>
    
=====================传参方式6:使用JSON传递参数========================
List<User> selectUser(JSONObject params)
<select id="selectUser" resultType="user" parameterType="com.alibaba.fastjson.JSONObject">
	select * from user where 1=1
    <if test="id != null">
        and id = #{
    
    id}
	</if>
    <if test="name != null">
        and name = #{
    
    name}
	</if>
</select>

========================复杂情况:参数类型为对象+集合========================
@Data
public class Department {
    
    
    private Long id;
    private String deptName;
    private String descr;
    private Date createTime;
    List<Employee> employees;
}
List <Employee> findByDepartment(@Param("department")Department department);
<select id="findByDepartment" resultMap="BaseResultMap" parameterType="com.wg.demo.po.Department">
    select * from employee where dept_id =#{
    
    department.id} and age in
    <foreach collection="department.employees" open="(" separator="," close=")" item="employee">
        #{
    
    employee.age}
    </foreach>
</select>

PS:方式4传递的参数是vo实体类,和 方式1 封装为一个map对象,甚至网上说的封装一个专门用来存参数的vo对象Condition.java,
其实具体做法都类似,是一个做法。

模糊查询

第1种:在Java代码中添加sql通配符%
string str = "%张%";
list<User> list = mapper.selectlike(str);
<select id="selectlike">
 select * from foo where bar like #{value}
</select>
   
第2种:在sql语句中拼接通配符%,但会引起sql注入
string str = "张";
list<User> list = mapper.selectlike(str);
<select id="selectlike">
     select * from user where name like '%${value}%'
</select>

全部标签

databaseIdProvider

mybatis-config.xml 的 <databaseIdProvider>标签会设置几个数据库的别名
    注意:此时需要引入 oracle 和 sqlserver 驱动jar
mapper.xml 中 databaseId="mysql" 动态指定一个value
    
<databaseIdProvider type="DB_VENDOR">
  <property name="MySQL" value="mysql"/>       
  <property name="Oracle" value="oracle" />
  <property name="SQL Server" value="sqlserver" />
</databaseIdProvider>

    <select id="selectUser" resultType="user" parameterType="user" databaseId="mysql">
        select * from user where 1=1
        <if test="_parameter != null">
            <!-- 单个参数时_parameter就是这个参数 对象参数时,会封装为map,通过_parameter.id传id -->
            and id = #{_parameter.id }
        </if>
        <if test="_databaseId == null">
            <!-- _databaseId 就是 databaseIdProvider 中 value 的值 -->
            and name = #{_databaseId }
        </if>
    </select>

bind,if

    <!--    bind标签可以使用OGNL表达式创建一个变量并将其绑定到上下文中。
            需求:
                concat函数连接字符串,在MySQL中,这个函数支持多个参数,但在Oracle中只支持两个参数。
                由于不同数据库之间的语法差异,如果更换数据库,有些SQL语句可能就需要重写。针对这种情况
                可以使用 bind 标签来避免由于更换数据库带来的一些麻烦。
            <bind>属性:
                name:为绑定到上下文的变量名
                value:为OGNL表达式。
            使用bind标签拼接字符串不仅可以避免因更换数据库而修改SQL,也能预防SQL注入。    -->
    <!--原代码-->
    <if test="name !=null and name!=''">
        and name like concat('%',#{name},'%')
    </if>
    <!--改进后代码-->
    <if test="name !=null and userName!='' ">
        <bind name="userNameFinal" value="'%'+name+'%'"/>
        and name like #{nameFinal}
    </if>

concat标签,在mysql中,可以拼接多个字符,但是如果有一天,你的数据库突然变成了oracle,
concat就会报错了,因为它只能接受两个参数。
这个时候,bind同样适用,如下:
<if test=" userName != null and userName ! = ">
  and username like concat ( '1',#{userName},'2' )
</if>
可以改成:
<if test=" userName != null and userName !="> 
  <bind name= " userNameLike ” value = ”'1'+ userName + '2'”/>
  and username like #{userNameLike} 
</if> 
<select id="queryBlogIF" parameterType="map" resultType="com.rui.pojo.Blog">
    select * from mybatis.bolg where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

choose,when,otherwise

choose中的and条件,会自动处理是否需要拼接“and”问题
choose中只会选择一个满足的when生效
当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql
类似于Java 的 switch 语句,otherwise 则为 default

<select id="query" parameterType="map" resultType="user">
    select * from user
    <where>
        <choose>
            <when test="id != null">
                id =#{id}
            </when>
            <when test="name!=null">
                and name = #{name}
            </when>
            <otherwise>  and age = #{age}  </otherwise>
        </choose>
    </where>
</select>

where,set,trim

where之后所有条件都不符合,sql是select * from user where,<where>此时会自动去掉where保证语法正确	
select * from mybatis.bolg
<where>
	<if test="title != null">
	    title = #{title}
	</if>
	<if test="author != null">
	    and author = #{author}
	</if>
</where>
set会删除最后一个逗号,保证语法正确
<update id="updateBlog" parameterType="map">
    update user
    <set>  
        <if test="title != null">  title = #{title},  </if>
        <if test="author != null">  author = #{author},   </if>
    </set>
    where id = #{id}
</update>
<!-- 	有符合的条件,就自动加前缀where 
		prefixOverrides="and"会删除第一个and, 保证语法正确
		suffixOverrides=","会删除第一个, 保证语法正确 	-->
<trim prefix="where" prefixOverrides="and">
	<if test="state != null">
	  and 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>
</trim>

sql include

<sql id="common">
    <if test="title != null">
       and  title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

<select id="queryBlogIF" parameterType="map" resultType="user">
    select * from user where 1=1 
    <where>
        <include refid="common"></include>
    </where>
</select>

foreach

查询 id=1 or id=3 or id=5

select * from user where 1=1 and 
  <foreach item="id" index="index" collection="ids"
      open="(" separator="or" close=")">
        #{id}
  </foreach>
<!-- 表示从ids这个参数中取出id, 拼接成 (id=1 or id=2 or id=3)  -->

<!--  select * from mybatis.bolg where 1=1 and (id=1 or id=2 or id=3)
我们现在传递一个万能的map,这个map中可以存在一个map  -->
<select id="queryBlogForeach" parameterType="map" resultType="com.rui.pojo.Blog">
    select * from mybatis.bolg
    <where>
	    <foreach collection="ids" item="id" open="(" close=")" separator="or">
	        id = #{id}
	    </foreach>
    </where>
</select>
foreach元素的属性主要有 item,index,collection,open,separator,close
item表示集合中每一个元素进行迭代时的别名
index指定一个名字,用于表示在迭代过程中,每次迭代到的位置
open表示该语句以什么开始
separator表示在每次进行迭代之间以什么符号作为分隔符
close表示以什么结束

在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,
但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
1.如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2.如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3.如果传入的参数是多个的时候,我们就需要把它们封装成一个Map或者Object

再举一个例子

<update id="updateLxdh"  parameterType="java.util.HashMap">
      这种方式是执行了多个update语句,上一种方式是一个语句,速度肯定是单个语句快
      <foreach collection="list/array/map" item="item" index="index" separator=";">
              update hr_user_base_info set lxdh =#{item.lxdh} where usercode =#{item.usercode}
      </foreach>
  </update>

resultMap

当查到的结果集和parameterType中实体类字段不一致时,表示此时并不是一个单独的实体,而是多个属性的汇总了,就需要resultMap来表明具体某个属性对应sql的结果集中哪个字段

方式1:sql起别名
数据库是order_id,实体类属性是id

    <select id="selectOrder" parameterType="int" 
        resultType="me.gacl.domain.Order">
        select order_id id, order_no orderNo from orders where order_id=#{id}
    </select>

方式2:resultMap
结果集映射

    <!-- 根据id查询得到一个order对象,使用这个查询是可以正常查询到我们想要的结果的,
        这是因为我们通过<resultMap>映射实体类属性名和表的字段名一一对应关系 -->
    <select id="selectOrderResultMap" parameterType="int" resultMap="orderResultMap">
        select * from orders where order_id=#{id}
    </select>
    <!--    通过<resultMap>映射实体类属性名和表的字段名对应关系 -->
    <resultMap type="me.gacl.domain.Order" id="orderResultMap">
        <!-- 用id属性来映射主键字段 property是实体类属性,column是表字段-->
        <id property="id" column="order_id"/>
        <!-- 用result属性来映射非主键字段 -->
        <result property="orderNo" column="order_no"/>
        <!-- price属性名字段名是相同的,就没必要写了,可以注释掉 -->
        <result property="price" column="price"/>
    </resultMap>

对比Hibernate

Hibernate劣势

  • 代码虽然强大但过于简略,并不清楚到底做了什么,不利于排查问题
    作为程序员,我们希望每一行代码都知道它到底在干什么。尤其是数据库访问的代码,往往系统的瓶颈就在这些地方产生,每一行都不能小看。hibernate早期版本的部分代码,比我想象的复杂和强大很多。的确很多地方Hibernate可以强大的只用一行代码解决很多问题,这里既有hibernate本身的逻辑,也有你应用的逻辑,如果这一行产生了问题,很难排查原因。
  • 需求迭代后项目修改巨大
    开发时节约了很多时间,但是它对你的业务逻辑和数据模型依赖的太高了。短期没啥问题,但随着项目的变迁,在维持时,会发现你代码特别脆弱,随便改一处数据库,整个java项目可能要大改。

相比之下Mybatis对程序员更友好

猜你喜欢

转载自blog.csdn.net/vayne_xiao/article/details/109758222