【Java】Mybatis笔记

Mybatis

一、第一个Mybatis程序

思路:搭建环境–>导入Mybatis–>编写代码–>测试!

1.1、搭建环境
1.1.1、搭建数据库

image-20211007191416624

1.1 .2、新建项目
  • 新建一个普通Maven项目
  • 删除src目录,这样可以当一个父工程
  • 添加依赖,Mysql,MyBtatis,junit
<!--父工程 -->
    <groupId>com.yubao</groupId>
    <artifactId>Mybatis-Study</artifactId>
    <version>1.0-SNAPSHOT</version>
<!--依赖-->
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
1.2、创建模块

在父项目里面已经导好依赖,这样子模块就不用再次添加依赖

image-20211007194855775

1.2.1、配置Mybatis的核心配置文件

image-20211008151444721

我的mysql是8.0版本以上的,在driver那里需要加cj "& amp;"就是&的转义。

<?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 default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="******"/>
            </dataSource>
        </environment>
    </environments>

</configuration>
1.2.2、编写工具类

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
也可理解为工厂模式;

如果说每次操作都要去读取一下配置文件;是比较麻烦的,那么就写个工具类把要使用的的功能封装起来吧;每次操作去调用工具类;

在utils目录下创建工具类MybatisUtils

package com.yubao.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {

    private  static SqlSessionFactory sqlSessionFactory=null;
    //在调用工具类时就执行;
    static {
        try {
            //获取SqlSessionFactory对象;

            //获得Mybatis配置文件;
            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(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
}
1.2.3、编写实体类

在pojo下面编写一个实体类,在这里我使用了lombok,只需要在插件里面下载,重启,然后在pom文件中添加依赖即可

只需要添加一个@Data就能省去get set的代码

package com.yubao.pojo;

import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private String password;

    public User(){

    }

    public User(int id,String name ,String password){
        this.id=id;
        this.name=name;
        this.password=password;
    }
}
1.2.4、编写Dao持久层

这里用的接口对应配置文件的方式;
先用UserDao命名吧;这个和之后要用的Mapper是一样的

package com.yubao.dao;

import com.yubao.pojo.User;

import java.util.List;

public interface UserDao {

    List<User> findAlluser();

}

需要注意的是,现在不用去写实现类去实现接口的方式,那样子比较麻烦;
比说说写个查询方法把;要获取结果集就得需要一个一个去写setXXX,getXXX对应属性;而且每次写一个方法,就得写对应的;

好了,接着回到这个简单的项目;创建个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的空间对应创建的持久层接口;-->
<mapper namespace="com.yubao.dao.UserDao">

    <!--比如说,现在要写个查询语句,id就对应接口的方法;-->
    <!--resultType: 查询的结果类型-->

    <select id="findAllUser" resultType="com.yubao.pojo.User">
        select * from mybatis.user;
    </select>

</mapper>

这时候就可以直接去测试了

1.3、测试

我们使用junit来进行测试

package com.yubao.dao;

import com.yubao.pojo.User;
import com.yubao.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {

    @Test
    public void test(){

        //第一步:获取SqlSession对象
        SqlSession sqlSession =MybatisUtils.getSqlSession();

        //方式一:getMapper
        UserDao userDao= sqlSession.getMapper(UserDao.class);

        List<User> userList=userDao.findAllUser();

        for(User user:userList){
            System.out.println(user);
        }

        sqlSession.close();

    }


}
  • 如果我们直接运行,他会报错,因为我们没有将这个UserMapper.xml文件去注册,他找不到我们这个文件,去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 default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="*"/>
            </dataSource>
        </environment>
    </environments>

    <!--每个mapper都需要在核心配置文件中注册-->
    <mappers>
        <mapper resource="com/yubao/dao/UserMapper.xml"/>
    </mappers>

</configuration>
  • 注册好之后我们运行仍会报错,这是因为我们在之前学Maven的时候提到过,因为过滤的问题他不会去java目录下寻找我们的配置文件,我们需要自己去设置。只需要在父项目的pom文件里添加这段代码,仍然报错的话需要去子模块里添加
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.yml</include>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.yml</include>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
1.4、CRUD操作
  • 注意事项:
    namespace中的包名要和Dao/mapper接口的包名一致。
1.4.1、select

选择、查询语句:

  • id:就是对应的namespace中的方法名
  • resultType:Sql语句执行的返回类型
  • parameterType:Sql语句中传入参数的类型

之前演示了查询的操作,这里就试试增删改,这三个操作是要开启事物的

根据id查询用户:

//UserMapper接口部分
User getUserById(int id); 

//UserMapper xml文件部分
<select id="getUserById"  parameterType="int"resultType="com.yubao.pojo.User">
        select * from mybatis.user where id=#{id};
</select>
      
      
 @Test
public void testSelectId(){

        SqlSession sqlSession=MybatisUtils.getSqlSession();

        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);

        User user=userMapper.getUserById(1);

        System.out.println(user);
    }
1.4.2、增、删、改用户
    <!--添加用户 -->
    <insert id="addUser" parameterType="com.yubao.pojo.User">
        insert into  mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
    </insert>


    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id};
    </delete>

    <update id="updateUser" parameterType="com.yubao.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
    </update>
      
      
          @Test
    public void testAdd(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();

        UserMapper userDao=sqlSession.getMapper(UserMapper.class);

        int i=userDao.addUser(new User(4,"大王","122121"));

        if(i>0){
            System.out.println("添加成功!!!");
        }
        //提交事物
        sqlSession.commit();

        //关闭sqlSession
        sqlSession.close();
    }


    @Test
    public void testDeleteUser(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();

        UserMapper userMapper =sqlSession.getMapper(UserMapper.class);

        if(userMapper.deleteUser(3)>1){
            System.out.println("删除成功!");
        }

        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        SqlSession sqlSession =MybatisUtils.getSqlSession();

        UserMapper userMapper =sqlSession.getMapper(UserMapper.class);

        if(userMapper.updateUser(new User(2,"TEst","1211"))>1){
            System.out.println("修改成功!!");

        }
        sqlSession.commit();
        sqlSession.close();
    }


1.5、万能的Map

当我们查询的时候,需要传递参数,有时候参数比较多,或者我们update的时候,我们可能不用一整个类,只用这个类的几个字段,这时候就可以用到Map

    int updateUser2(Map<String , Object> map);

    <update id="updateUser2" parameterType="map">
        update mybatis.user set name=#{username} where id=#{id};
    </update>

    @Test
    public void updateUser2(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();

        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);

        Map<String,Object> map=new HashMap<String ,Object>();
        map.put("username","MapTest");
        map.put("id",2);

        if(userMapper.updateUser2(map)>1){
            System.out.println("修改成功!!");
        }
        sqlSession.commit();
        sqlSession.close();
    }

  • Map 传递参数,直接在sql中取出key即可! parameterType=“map”

  • 对象传递参数,直接在sql中取对象的属性即可!parameterType=“Object”

  • 只有一个基本类型参数的情况下,可以直接在sql中取到

  • 多个参数用Map,或者注解

1.6、模糊查询
  • Java代码在执行的时候,传递通配符% %

    List<User> userList =mapper.getUserLike("%李%");
    
  • 在sql拼接时使用通配符

    select * from mybatis.user where name  like "%#{value}%"
    
1.7、Sql注入问题

在学习Mysql的时候我们接触到了Sql注入问题,我们在使用Statement的时候用的是拼接字符串

String sql = "delete from table1 where id="+id;

如果有人将id的内容改为“3 or 1=1”。那么表中的任何记录都将被删除,后果十分严重。

所以我们学习使用了PreStatement

string sql = "delete from table1 where id=?"//构建sql语句,以?作为占位符,这个位置的值待设置
PreparedStatement pst = con.preparedStatement(sql);    //创建PreparedStatement时就传入sql语句,实现了预编译
pst.setString(1,"3");

那么为什么PreparedStateement能防止Sql注入,或者说预编译为什么能防止Sql注入?

在使用Statement的时候是不进行预编译也就是我们将有sql注入拼接好的字符串拿去编译,那么它就会编译出Sql注入想要的效果(这时候 or这类的语法功能会被编译出来),然后拿去执行。不要将Sql语句简单的看为执行这一个操作,编译会将语句编译成电脑看得懂的,也就是Sql注入发生在编译阶段,然后电脑再去执行。

使用PreparedStatement时,会进行预编译,那么这时候编译出来的就是我们想要的效果,我们把参数传进去然后就会拿去执行,含有or的这一串被限定为字符串或其他的数据类型,使参数里有敏感字符如 or '1=1’也会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令,因为缺少编译这一阶段,不再具有语法功能

Sql发送到服务器大致会有如下流程:

  1. 解析阶段
  2. 编译阶段
  3. 优化阶段
  4. 缓存阶段
  5. 执行阶段

PrepareStatement发送到服务器后会经历上述1、2、3、4过程,PrepareStatement并不是完整的sql语句,在执行之前还需要进行用户数据替换。

在填入用户数据时,PrepareStatement已经经历了上述过程,就不会重新编译,用户的数据只能作为数据进行填充,而不是sql的一部分。服务器从缓存中获得已经编译优化后的语句,替换掉用户数据执行,避免了sql注入。

引用自:How prepared statement prevents SQL Injection?

1、#{} 是预编译处理,mybatis在处理时,会将?替换为输入参数的值,然后调用PrepareStatement的set方法来赋值,传入字符串时,会在字符串的两端加上单引号,使用占位符的方式提高效率,防止SQL注入。
2、 : 表 示 S Q L 拼 接 , 将 接 收 到 的 参 数 内 容 不 加 任 何 修 饰 的 拼 接 到 S Q L 中 , 可 能 引 起 S Q L 注 入 。 {}:表示SQL拼接,将接收到的参数内容不加任何修饰的拼接到SQL中,可能引起SQL注入。 SQLSQLSQL{}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。

现在我们来看两种模糊查询的方式,为什么第一种能防止Sql注入能?

因为我们在使用第一种方式的时候,在传入参数之前就进行了预编译

二、配置解析

2.1、核心配置文件

一般命名为 mybatis-config.xml

configuration(配置)

    properties(属性)
    settings(设置)
    typeAliases(类型别名)
    typeHandlers(类型处理器)
    objectFactory(对象工厂)
    plugins(插件)
    environments(环境配置)
        environment(环境变量)
            transactionManager(事务管理器)
            dataSource(数据源)
    databaseIdProvider(数据库厂商标识)
    mappers(映射器)

2.2、 environments(环境配置)
  • Mybatis可以配置成适应多种环境
  • 尽管可以配置多个环境,但是每个SqlSession实例只能选择一种环境。
  • 学会配置多套运行环境
  • Mybatis默认的事务管理器就是JDBC, 连接池 POOLED
2.3、 properties(属性)

之前我们JDBC用的db.properties 这个配置文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username=root
password=123456

在核心配置文件中映入

<!--引入外部配置文件-->
<properties resource="db.properties"> 
  <!--可以在其中增加一些属性配置-->
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
  
<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>
  • db.properties放在resource目录下,可以不用写路径
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 优先级问题:如果两个文件有同一个字段,优先使用外部配置文件的!
2.4、typeAliases(类型别名)

类型别名是为Java类型设置的一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

 <select id="findAllUser" resultType="com.yubao.pojo.User">
        select * from mybatis.user;
 </select>

这里面的“com.yubao.pojo.User”这个名字很长,我们可以给它起个别名。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sb9e6y84-1647934165063)(/Users/sleeping/Library/Application Support/typora-user-images/image-20211028095928994.png)]

在配置问题件添加好别名之后就可以在Mapper里面使用。上面的代码就可以改为

 <select id="findAllUser" resultType="com.yubao.pojo.User">
        select * from mybatis.user;
 </select>

image-20220123155524418

也可以指定一个包名,MyBatis会在包名下面搜索需要的JavaBean

<typeAliases>
<package name="com.yubao.pojo"/>
</typeAliases>

他会扫描包下的每一个类,并用类名的首字母小写作为它的别名,我们可以直接使用。若有注解,则别名为其注解值。

@Alias ("DearUser")
public class User {
...
}

在类比较少的情况下,使用第一种起别名的方法

在类比较多的情况下,使用第二种起别名的方法

2.5、setting(设置)

image-20211028101525944

image-20211028101600250

2.6、其他配置
  • typeHandlers(类型处理器)
    objectFactory(对象工厂)
    
  • plugins(插件)
    - mybatis-generator-core(自动写mybatis代码)
    - mybatis-plus (更加简化的mybatis)
    - 通用mapper
    
2.7、mappers(映射器)

第一种映射方式:

image-20211028125051030

xml文件可以放在任何位置只要能够找到就行。

第二种方式:使用class文件绑定注册

    <mappers>
        <mapper class="com.yubao.dao.UserMapper"></mapper>
    </mappers>
  • 接口和XML文件必须同名
  • 接口文件和XML文件必须放在同一文件夹

第三种方式: 使用扫描包绑定

    <mappers>
        <package name="com.yubao.dao"/>
    </mappers>
  • 接口和XML文件必须同名
  • 接口文件和XML文件必须放在同一文件夹
2.8、生命周期和作用域

image-20211028131032027

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

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory,就不再需要它
  • 所以使用局部变量

SqlSessionFactory:

  • 说白了就是类似数据库连接池
  • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • SqlSessionFactory 的最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接到连接池的一个请求
  • 需要开启跟关闭
  • SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完之后需要赶紧关闭,否则资源被占用
  • image-20211028174100418

image-20211028131953072

每一个mapper就对应一个业务!

三、解决数据库字段和类属姓名不一致问题

3.1、问题

image-20211028175505023

可以看到数据库里面的是pwd,而实体类里面的是password,下面是我们的sql语句

    <select id="findAllUser" resultType="com.yubao.pojo.User">
        select * from mybatis.user;
    </select>

完整的其实是这样的:

select id,name,pwd from mybatis.user;

那么从这里就可以看出来为什么我查出来的password为null。由此我们也可以想出一个解决方法就是起别名。

3.2、解决方法:
1:起别名
<select id="findAllUser" resultType="com.yubao.pojo.User">
    select  id,name,pwd as password from mybatis.user;
</select>
2:resultMap 简单使用
    <resultMap id="userMap" type="User">
        <result column="pwd" property="password"></result>
    </resultMap>

    <select id="findAllUser" resultMap="userMap">
        select *  from mybatis.user;
    </select>

column 对应的就是数据库里面的字段,property就是对应类的属性,id对应下面sql语句的resultMap,type对应类,这里User是在配置文件里面取的别名

  • resultMap元素是Mybatis中最重要最强大的元素
  • ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了
  • ResultMap最优秀的地方在于,虽然你已经对他相当了解了,但是更本就不需要显式地用到他们
  • 如果世界总是这么简单就好了,接下来会有更复杂的。
3.2、解决方法:
1:起别名
<select id="findAllUser" resultType="com.yubao.pojo.User">    select  id,name,pwd as password from mybatis.user;</select>
2:resultMap 简单使用
    <resultMap id="userMap" type="User">        <result column="pwd" property="password"></result>    </resultMap>    <select id="findAllUser" resultMap="userMap">        select *  from mybatis.user;    </select>

column 对应的就是数据库里面的字段,property就是对应类的属性,id对应下面sql语句的resultMap,type对应类,这里User是在配置文件里面取的别名

  • resultMap元素是Mybatis中最重要最强大的元素
  • ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了
  • ResultMap最优秀的地方在于,虽然你已经对他相当了解了,但是更本就不需要显式地用到他们
  • 如果世界总是这么简单就好了,接下来会有更复杂的。
3:自动驼峰命名

数据库字段和实体类字段有对应关系,这里的对应关系就是数据库字段全为大写字母且单词之间用_分隔,实体类的属性名采用小驼峰式命名,一定要保证对应,例如数据库中的USER_ID对应实体类中的·userId字段。

类似于如下:

在这里插入图片描述

这种不对应的情况,Mybatis提供了一个自动驼峰命名规则的设置,但是默认是关闭的,所以当我们没有设置的时候,这样也是对应不上的。我们就需要在Mybatis的配置文件中添加如下配置:

<settings>
    <!--  开启自动驼峰命名规则映射  -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

这里需要注意的就是settings标签的位置了,需要按照以下顺序排列

在这里插入图片描述

四、日志

4.1、日志工厂

image-20211028192645308

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

image-20211028192852818

4.2、Log4j
  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、
  • 我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

我们现在pom文件里面添加依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

上面提到我们可以通过一个配置文件来灵活的进行配置

我们在resoures里面添加一个log4j.properties文件,下面是我从网上找来的一个配置文件

### 设置日志级别
log4j.rootLogger=info,stdout,D,R

### 输出到控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c:%L]-[%p] %m%n

### 输出到日志文件
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
## 异常日志文件名
log4j.appender.D.File=.log/yubao.log
log4j.appender.D.DatePattern='.'yyyy-MM-dd
log4j.appender.D.Append = true
## 输出INFO级别以上的日志
log4j.appender.D.Threshold=INFO
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c:%L]-[%p] %m%n

### 保存异常信息到单独文件
log4j.appender.R = org.apache.log4j.DailyRollingFileAppender
## 异常日志文件名
log4j.appender.R.File = .log/yubao.log
log4j.appender.R.DatePattern='.'yyyy-MM-dd
log4j.appender.R.Append = true  
## 只输出ERROR级别以上的日志!!!
log4j.appender.R.Threshold= ERROR
log4j.appender.R.layout = org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern =%-d{yyyy-MM-dd HH:mm:ss} [%c:%L]-[%p] %m%n

接下来配置log4j为日志实现

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

接下来测试一下,我们测试的信息就会有一个日志生成,在对应路径下也可以找到文件

image-20220123160038220

简单使用:

用日志来代替sout,日志相比于sout有许多优点,我们之前配置了这个日志可以写在文件里,所以我们用日志输出的时候,日志文件里也可以找到,并且日志有优先级等等。

@Test
public void logTest(){
    logger.info("this is a info");
    logger.debug("this is an debug");
    logger.error("this is an error");
}

image-20211029155714113

五、分页

Sql分页

使用Limit分页

select *  from mybatis.user limit #{startIndex},#{pageSize};

使用Mybatis分页

  • 接口

    List<User> selectUserByLimit(Map<String,Integer> map);
    
  • Mapper.xml

    <select id="selectUserByLimit"  parameterType="map" resultMap="userMap">
        select *  from mybatis.user limit #{startIndex},#{pageSize};
    </select>
    
  • 测试

    @Test
    public void testimit(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();
    
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        HashMap<String,Integer> map=new HashMap<String, Integer>();
    
        map.put("startIndex",0);
        map.put("pageSize",2);
    
        List<User> userList=userMapper.selectUserByLimit(map);
    
        for(User user:userList){
            System.out.println(user);
        }
    
    
        sqlSession.close();
    }
    
Java分页

之前本质上还是利用sql来实现分页,不符合面相对象的思想,我们可以利于Rowbounds这个类来实现分页,但是比第一种方法慢,项目里面不推荐这种方法。

<select id="findAllUser" resultMap="userMap">
    select *  from mybatis.user;
</select>

前面的代码跟之前查询所有 用户都是一样的,我们只需要在测试的时候改动

@Test
public void testSelect(){

    RowBounds rowBounds =new RowBounds(0,2);

    SqlSession sqlSession =MybatisUtils.getSqlSession();

    List<User> userList=sqlSession.selectList("com.yubao.dao.UserMapper.findAllUser",null,rowBounds);

    for(User user:userList){
        System.out.println(user);
    }

    sqlSession.close();

}

六、使用注解开发

  1. 注解在接口上实现

    public interface UserMapper {
        @Select("select * from user")
        List<User> getUser();
    }
    
  2. 在配置文件中绑定

        <mappers>
            <mapper class="com.yubao.dao.UserMapper"/>
        </mappers>
    

    使用注解开发能够简化流程,但是一些复杂的操作就完成不了,比如之间的数据库字段与类属性名不一样的问题。

CRUD

在使用注解查询的时候

  • 有多个参数的时候,需要再基本类型和String的前面加上@Param()
  • Sql语句里面的参数是看@Param()里面的
  • 引用类型不需要@Param
  • 只有一个参数的时候也不需要@Param
    @Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})")    int insertUser(User user);
    @Select("select * from user where id =#{id} and name =#{name}")    User getUserById(int id,String name);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4ITvl34-1638790994410)(/Users/sleeping/Library/Application Support/typora-user-images/image-20211122204000255.png)]

七、Lombok

  • @Getter/@Setter: 作用类上,生成所有成员变量的getter/setter方法;作用于成员变量上,生成该成员变量的getter/setter方法。可以设定访问权限及是否懒加载等。

  • @ToString:作用于类,覆盖默认的toString()方法,可以通过of属性限定显示某些字段,通过exclude属性排除某些字段。

  • @EqualsAndHashCode:作用于类,覆盖默认的equals和hashCode

  • @Data:作用于类上,是以下注解的集合:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor

  • @AllArgsConstructor:生成全参构造器

  • **@NoArgsConstructor:生成无参构造器;**q

八、多对一处理

新建两张表

    CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=INNODB
    DEFAULT CHARSET=utf8INSERT 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=utf8INSERT 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);

对于多对一的情况我们又两种方法。

按照查询嵌套处理

首先是按照查询嵌套处理,这个对应的就是SQL里面的子查询

    <select id="getStudent" resultMap="StudentTeacher">select * from student</select>
    <resultMap id="StudentTeacher" type="com.yubao.pojo.Student">   
        <!--property就是实体类的属性,column就是数据库里面的字段名-->
        <result property="id" column="id"></result>
        <result property="name" column="name"/>
        <!--这里student实体类里面有一个Teacher类,我们利用association来处理-->
        <association property="teacher" column="tid" javaType="com.yubao.pojo.Teacher" select="getTeacher"/>
    </resultMap>
    <select id="getTeacher" resultType="com.yubao.pojo.Teacher">select * from teacher where id=#{id};</select>

思路:

  • 查询所有学生信息
  • Student里面的Teacher类与查询出来的tid不对应,我们就利用查询

根据resultMap的设计思想:

ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了

我们可以简化上面的代码,将resultMap里面的前两个result删掉,最后也能成功运行。

按照结果嵌套处理
    <select id="getStudent2" resultMap="StudentTeacher2">select s.id sid,s.name sname,t.name tname,tid from student s
        ,teacher t where s.tid=t.id;
    </select>
    <resultMap id="StudentTeacher2" type="com.yubao.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="com.yubao.pojo.Teacher">
            <result property="name" column="tname"/>
            <result property="id" column="tid"/>
        </association>
    </resultMap>

这个其实比上面的那个容易理解,这个使用resultMap把我们的Sql语句查询出来的结果与实体类做一个映射。Sql语句就是我们常用的联表查询,将查询的字段起个别名,然后再resultMap里面使用这个别名映射到Student的属性上。

九、一对多处理

@Datapublic
class Teacher {  
    private int id;  
    private String name;  
    private List<Student> students;
}
按照查询结果嵌套处理
    <select id="getTeacher" resultMap="TeacherStudent">select s.id sid ,s.name sname,t.id tid,t.name tname from teacher
        t,student s where tid=s.tid;
    </select>
    <resultMap id="TeacherStudent" type="com.yubao.pojo.Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="com.yubao.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
        </collection>
    </resultMap>
按照结果嵌套处理
    <select id="getTeacher2" resultMap="TeacherStudent2">select * from teacher where id=#{tid}</select>
    <resultMap id="TeacherStudent2" type="com.yubao.pojo.Teacher">
        <collection
                    property="students" 
                    javaType="ArrayList"
                    ofType="com.yubao.pojo.Student"
                    select="getStudentByTeacher" 
                    column="id">
        </collection>
    </resultMap>
    <select id="getStudentByTeacher" resultType="com.yubao.pojo.Student">
        select * from student where tid=#{id};
   </select>

由于我们一个Teacher有许多Student,所以我们查出来的是一个集合,我们使用Collection而不是Association,Collection里面有一个ofType就是这个集合的里面包含的类。

十、动态SQL

10.1、环境搭建

在Mybatis文件已经配置好的情况下

  1. MySql创建一张表格
    CREATE TABLE `blog`(`id` VARCHAR(50) NOT NULL ,`title` VARCHAR(100) NOT NULL,`author` VARCHAR(30) NOT NULL
    ,`create_time` DATETIME NOT NULL ,`views` INT(30) NOT NULL)ENGINE=INNODB DEFAULT CHARSET=utf8;
  1. 编写实体类
    @Datapublic
    class Blog {
        private String id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
    }

  1. 编写实体类对应的Mapper接口和Mapper.xml文件

在实体类的属性名与数据库的字段名不相同的时候,如果实体类里面使用的是根据数据库里面进行驼峰命名来的,我们可以在配置文件里设置

    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="mapUnderscoreToCamelCase" value="True"/>
    </settings>
10.2、IF

在我们之前学习JavaWeb的时候,用JDBC来在java代码里面判断一些参数是否有值,如果有值那么就进行拼接SQL语句,来实现查询等操作。在Mybatis里我们可以更方便的实现这一操作。

    <select id="selectBlog" parameterType="map" resultType="com.yubao.pojo.Blog">select * from blog where 1=1
        <if test="title!=null">and title=#{title}
        </if>
        <if test="author!=null">and author =#{name}</if>
    </select>

我们在写Mapper.xml文件的时候使用if就可以实现上述操作,

    @Test
    public void TestFour() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        map.put("title", "Mybatis");
        for (Blog blog : blogMapper.selectBlog(map)) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

我们在map里面放不同的参数就可以查询出不同的结果出来,不用在java代码里判断,在进行方法的重载等操作,使用if就可以实现sql代码的复用。

10.3、where set

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除

上面我们在学习if的时候我们的sql语句里面的 where 1=1这里就是为了防止,如果map里面有title传进来不会出现 where and 这种情况。我们使用where标签就可以解决这个问题

    <select id="selectBlog" parameterType="map" resultType="com.yubao.pojo.Blog">select * from blog
        <where>
            <if test="title!=null">and title=#{title}</if>
            <if test="author!=null">and author =#{name}</if>
        </where>
    </select>

我们在使用update语句时候也会遇到一些问题就是set里面的逗号

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

    <update id="updateAuthorIfNecessary">update Author
        <set>
            <if test="username != null">username=#{username},</if>
            <if test="password != null">password=#{password},</if>
            <if test="email != null">email=#{email},</if>
            <if test="bio != null">bio=#{bio}</if>
        </set>
        where id=#{id}
    </update>
10.4、chose

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

    <select id="findActiveBlogLike" resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’
        <choose>
            <when test="title != null">AND title like #{title}</when>
            <when test="author != null and author.name != null">AND author_name like #{author.name}</when>
            <otherwise>AND featured = 1</otherwise>
        </choose>
    </select>

传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)

什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句

10.5、SQL片段

有的时候,我们可能将一些功能的部分抽取出来,方便复用!

  1. 使用SQL标签抽取公共的部分
<sql id="if-title-author">    <if test="title!=null" >        and title=#{title}    </if>    <if test="author!=null">        and author =#{author}    </if></sql>
  1. 在需要使用的地方使用Include标签引用即可
    <sql id="if-title-author">
        <if test="title!=null">and title=#{title}</if>
        <if test="author!=null">and author =#{author}</if>
    </sql>

注意事项:

  • 最后基于单表来定义Sql片段
  • 不要存在where标签
10.6、foreach
    <select id="selectBlog" parameterType="map" resultType="com.yubao.pojo.Blog">select * from blog
        <where>id in     <foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach>
        </where>
    </select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

我们在写到sql语句里面 含有in的时候,如果in里面有几百个数据我们手写不知道要写多久,我们就可以通过一个集合将数据传进去,然后通过foreach拼接到sql语句里面

十一、缓存

  1. 什么是缓存?

    • 存在内存汇中的临时数据
    • 将用户经常查询的数据放在缓存中,用户查询数据就不用从磁盘上查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
  2. 为什么使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存?

    • 经常查询并且不经常改变的数据
11.1、Mybatis缓存
  • Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。

  • Mybatis系统默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,只有一级缓存开机(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
    • 为了提高拓展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。
  • 针对select

11.2、一级缓存

一级缓存的作用域是SqlSession

首次执行它时从数据库获取的所有数据会被存储在一段高速缓存中,今后执行这条语句时就会从高速缓存中读取结果,而不是再次查询数据库。MyBatis提供了默认下基于Java HashMap的缓存实现。

这条语句的执行结果被缓存,以后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQL。但是一旦执行新增或更新或删除操作,缓存就会被清除。下面将分情况来验证一下

第1种情况:同个session进行两次相同查询
    public class Test {
        public static void main(String[] args) throws Exception {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session = sqlSessionFactory.openSession();
            Person p1 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
            Person p2 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
            session.commit();
            session.close();
        }
    }

结论:MyBatis只进行1次数据库查询。

==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
<
Total: 1
User{id=1, name=‘Kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
User{id=1, name=‘Kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}

第2种情况:同个session进行两次不同的查询。
    public class Test {
        public static void main(String[] args) throws Exception {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session = sqlSessionFactory.openSession();
            Person p1 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
            Person p2 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 2);
            session.commit();
            session.close();
        }
    }

结论:MyBatis进行两次数据库查询。

==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
<
Total: 1
User{id=1, name=‘kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
==> Preparing: select * from t_person where id = ?
> Parameters: 2(Integer)
<
Total: 1
User{id=2, name=‘Jim’, age=50, birthday=Sat Dec 06 17:12:01 CST 2010}

第3种情况:不同session,进行相同查询。
    public class Test {
        public static void main(String[] args) throws Exception {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session1 = sqlSessionFactory.openSession();
            Person p1 = session1.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
            SqlSession session2 = sqlSessionFactory.openSession();
            Person p2 = session2.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 2);
            session1.commit();
            session2.commit();
            session1.close();
            session2.close();
        }
    }

结论:MyBatis进行两次数据库查询。

==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
<
Total: 1
User{id=1, name=‘kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
==> Preparing: select * from t_person where id = ?
> Parameters: 2(Integer)
<
Total: 1
User{id=2, name=‘Jim’, age=50, birthday=Sat Dec 06 17:12:01 CST 2010}

第4种情况:同个session,查询之后更新数据,再次查询相同的语句。
    public class Test {
        public static void main(String[] args) throws Exception {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session = sqlSessionFactory.openSession();
            Person p = null;
            p = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
            p.setAge(30);
            session.update("cn.mybatis.mydemo.mapper.PersonMapper.updatePerson", p);
            p = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
            session.commit();
            session.close();
        }
    }

结论:更新操作之后缓存会被清除:

==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
<
Total: 1
User{id=1, name=‘kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
==> Preparing: update t_person set age = ? where ID = ?
> Parameters: 30(Integer), 1(Integer)
<
Updates: 1
==> Preparing: sselect * from t_person where id = ?
> Parameters: 1(Integer)
<
Total: 1
User{id=1, name=‘kit’, age=30, birthday=Sun Oct 12 23:20:13 CST 2010}

小结

很明显,以上各种情况验证了一级缓存的概念,在同个SqlSession中,查询语句相同的sql会被缓存,但是一旦执行新增或更新或删除操作,缓存就会被清除。

引用:http://www.mybatis.cn/archives/744.html

11.3、二级缓存

既然有了一级缓存,那么为什么要提供二级缓存呢?我们知道,在一级缓存中,不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。

另外,Spring和MyBatis整合时,每次查询之后都要进行关闭sqlsession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。

步骤:

  • 开启全局缓存
<!--显式的开启全局缓存--><setting name="cacheEnabled" value="true"/>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值