MyBatis

1. MyBatis概述

  1. MyBatis简介

    1. MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
    2. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
    3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
    4. MyBatis是一个半自动化的持久化层框架
  2. JDBC

    1. SQL夹在Java代码块里,耦合度高导致硬编码内伤
    2. 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
  3. Hibernate和JPA

    1. 长难复杂SQL,对于Hibernate而言处理也不容易
    2. 内部自动生产的SQL,不容易做特殊优化
    3. 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降
  4. 对开发人员而言,核心sql还是需要自己优化
  5. sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据

2. HelloWorld

  1. 环境搭建

    1. 创建一个Java工程
    2. 创建测试库、测试表,以及封装数据的JavaBean,和操作数据库的接口
    3. 写MyBatis的主配置文件
    4. 写SQL映射文件
  1. <dependencies>
  2. <dependency>
  3. <groupId>org.mybatis</groupId>
  4. <artifactId>mybatis</artifactId>
  5. <version>3.4.5</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>mysql</groupId>
  9. <artifactId>mysql-connector-java</artifactId>
  10. <version>5.1.47</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>junit</groupId>
  14. <artifactId>junit</artifactId>
  15. <version>4.12</version>
  16. <scope>test</scope>
  17. </dependency>
  18. <dependency>
  19. <groupId>log4j</groupId>
  20. <artifactId>log4j</artifactId>
  21. <version>1.2.12</version>
  22. </dependency>
  23. </dependencies>
<!--主配置文件-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--在全局配置文件中配置SQL映射文件-->
    <mappers>
        <mapper resource="mybatis/EmployeeDao.xml"/>
    </mappers>

</configuration>
<!--log4j.xml-->
<?xml version="1.0" encoding="GB2312" ?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">

<log4j:configuration debug="true">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>
<!--SQL映射文件-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace: 名称空间,写接口的全类名,相当于告诉MyBatis,这个配置文件是实现哪个接口的-->
<mapper namespace="com.siki.dao.EmployeeDao">

    <!--查询语句 id: 方法名,相当于这个配置是对哪个方法的实现  resultType: 指定方法运行后的返回值类型(查询操作必须指定)-->
    <!--#{属性名}: 代表取出传递过来的某个参数的值-->
    <select id="getEmpById" resultType="com.siki.bean.Employee">
        select * from employee where id = #{id}
    </select>

    <update id="updateEmp">
        update employee set name = #{name},gender = #{gender},email = #{email},d_id = #{d_id} where id = #{id}
    </update>

    <delete id="deleteEmp">
        delete from employee where id = #{id}
    </delete>

    <insert id="insertEmp">
        insert into employee(name,gender,email,d_id) values(#{name},#{gender},#{email},#{d_id})
    </insert>

</mapper>
public class Employee {

    private Integer id;

    private String name;

    private String gender;

    private String email;

    private Integer d_id;

    public Employee() {

    }

    public Employee(Integer id, String name, String gender, String email, Integer d_id) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.email = email;
        this.d_id = d_id;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getD_id() {
        return d_id;
    }

    public void setD_id(Integer d_id) {
        this.d_id = d_id;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", email='" + email + '\'' +
                ", d_id=" + d_id +
                '}';
    }
}

public interface EmployeeDao {

    public Employee getEmpById(Integer id);

    public int updateEmp(Employee employee);

    public int deleteEmp(Integer id);

    public int insertEmp(Employee employee);

}

public class HelloWorldTest {

    private SqlSessionFactory sqlSessionFactory;

    public void initSqlSessionFactory() {
        String resource="mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //测试查询
    @Test
    public void test01() {
        initSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
        Employee emp = mapper.getEmpById(2);
        System.out.println(emp);
    }

    //测试插入
    @Test
    public void test02() {
        initSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //SqlSession sqlSession = sqlSessionFactory.openSession(true);  //true: 表示自动提交
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
        int res = mapper.insertEmp(
                new Employee(null,"xinxin","1","1314420@123.com", 2));
        //注意: 增删改要手动提交
        sqlSession.commit();
        System.out.println(res);
    }
}

3. 全局配置文件

  1. mybatis-config.xml:指导MyBatis正确运行的一些全局设置

  2. HelloWorld细节

    1. 获取到的是接口的代理对象,MyBatis自动创建的
    2. SqlSessionFactory:只是为了创建SqlSession对象,只需要new一次即可
    3. SqlSession:相当于与数据库进行的一次交互,一次会话,就应该创建一个新的SqlSession
  3. 全局配置文件里面能写什么东西

    1. configuration 配置

      1. properties 属性

      2. settings 设置

      3. typeAliases 类型命名

      4. typeHandlers 类型处理器

        1. 我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型
          步骤:

          1. 实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
      5. 指定其映射某个JDBC类型(可选操作)
  4. 在mybatis全局配置文件中注册

    1. objectFactory 对象工厂

    2. plugins 插件

      1. 插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行
      2. 四大对象(相当于Spring的九大组件)
  5. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  6. ParameterHandler (getParameterObject, setParameters)
  7. ResultSetHandler (handleResultSets, handleOutputParameters)
  8. StatementHandler (prepare, parameterize, batch, update, query)

    1. environments 环境

    2. environment 环境变量

    3. transactionManager 事务管理器

    4. dataSource 数据源

    5. databaseIdProvider 数据库厂商标识

      1. MyBatis 可以根据不同的数据库厂商执行不同的语句

      2. Type: DB_VENDOR 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识,也可以实现DatabaseIdProvider接口来自定义

      3. Property-name:数据库厂商标识

      4. Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用

    6. mappers 映射器







```java
public interface EmployeeDaoAnnoatation {

    @Select("select * from employee where id = #{id}")
    public Employee getEmpById(Integer id);

}

4. SQL映射文件

  1. SQL映射文件:相当于是对Dao接口的一次实现描述

  2. SQL映射文件能写什么

    1. cache –命名空间的二级缓存配置
    2. cache-ref – 其他命名空间缓存配置的引用
    3. resultMap – 自定义结果集映射
    4. parameterMap – 已废弃!老式风格的参数映射
    5. sql –抽取可重用语句块
    6. insert – 映射插入语句
    7. update – 映射更新语句
    8. delete – 映射删除语句
    9. select – 映射查询语句
  3. 获取插入数据后的自增id和不自增id ```xml

select max(id) + 1 from employee insert into employee(id,name,gender,email,d_id) values(#{id},#{name},#{gender},#{email},#{d_id})


```java
@Test
public void test02(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
        Employee employee = new Employee(null, "haha", "1", "11@123.com", 2);
        int res = mapper.insertEmp(employee);
        System.out.println("--->" + res);
        System.out.println("id---> " + employee.getId());
        sqlSession.commit();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}
  1. 参数的各种取值

    <!--
         传参:
             1. 单个参数
                 1. 基本类型: 取值#{随便写}
                 2. POJO
    
             2. 多个参数
                 1. 如果使用#{参数名}进行取值是无效的
                    select * from employee where id = #{id} and name = #{name}  这样查会报以下的错:
                         Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found.
                         Available parameters are [arg1, arg0, param1, param2]
                 2. 可用: 使用arg0,arg1(索引)或者是param1,param2(第几个参数)
                    原因: 只要传入了多个参数,mybatis就会自动将这些参数封装在一个map中,封装时使用的key
                         就是参数的索引和参数的标识(第几个参数),#{key}就是从map中取值
                    修改后: select * from employee where id = #{param1} and name = #{param2}
                3. 如果我们坚持使用属性名,可以使用@Param为参数指定key,告诉mybatis,使用我们标注的key
                   public Employee getEmpByIdAndName(@Param("id") Integer id,@Param("name") String name);
    
             3. POJO(JavaBean)
                 直接使用#{bean的属性名}进行取值
    
             4.map: 我们可以直接传入map,将多个参数封装起来
                 public Employee getEmpByIdAndName(Map<String,Object> map);
                 直接使用#{key}进行取值
    
            5. 扩展: 多个参数,自动封装成map
               method(@Param("id") Integer id,String name,Employee employee)
                  id: #{id}
                  name: #{param2}
                  employee: #{param3.属性名}
     -->
    <select id="getEmpByIdAndName" resultType="com.siki.bean.Employee">
     select * from employee where id = #{id} and name = #{name}
     <!--修改后就没问题了-->
     <!--select * from employee where id = #{param1} and name = #{param2}-->
    </select>
    
public interface EmployeeDao {

    public Employee getEmpById(Integer id);

    public int updateEmp(Employee employee);

    public int deleteEmp(Integer id);

    public int insertEmp(Employee employee);

    public Employee getEmpByIdAndName(@Param("id") Integer id,@Param("name") String name);

    public Employee getEmpByIdAndName(Map<String,Object> map);

}


//根据id和name查询对象
@Test
public void test03(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
        Employee employee = mapper.getEmpByIdAndName(1,"kitty");
        System.out.println(employee);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}

//使用map封装对象
@Test
public void test04(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
        Map<String,Object> map = new HashMap<>();
        map.put("id",1);
        map.put("name","kitty");
        Employee employee = mapper.getEmpByIdAndName(map);
        System.out.println(employee);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}
  1. 参数处理

    1. {key}:获取参数的值,预编译到SQL中,安全

    2. ${key}:获取参数的值,拼接到SQL中,有SQL注入问题 ```java / select from employee where id = ${id} and name = #{name} 编译后的sql语句: select * from employee where id = 1 and name = ?

select from employee where id = #{id} and name = #{name} 编译后的sql语句: select from employee where id = ? and name = ?

${}的应用场景: 一般都使用#{},安全,但是在不支持参数预编译的位置要进行取值,就使用${} select from ${tableName} where id = #{id} and name = #{name} /



6. 
查询返回List
```xml
<!--如果返回的是集合,resultType写的是集合里面元素的类型-->
<select id="getEmps" resultType="com.siki.bean.Employee">
    select * from employee
</select>
//查询所有Emplouee对象
@Test
public void test05(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
        List<Employee> emps = mapper.getEmps();
        System.out.println(emps);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}
  1. 查询返回map ```xml
```java public Map<String ,Object> getEmpByIdReturnMap(Integer id); //key就是这个记录的主键,value就是这条记录封装好的对象 @MapKey("id") //将id作为key,封装成map public Map<Integer,Employee> getEmpsReturnMap(); //单条记录封装为map @Test public void test06(){ SqlSession sqlSession = null; try { initSqlSessionFactory(); sqlSession = sqlSessionFactory.openSession(); EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); Map<String, Object> map = mapper.getEmpByIdReturnMap(1); System.out.println(map); } catch (IOException e) { e.printStackTrace(); }finally { sqlSession.close(); } } //多条记录封装为map @Test public void test07(){ SqlSession sqlSession = null; try { initSqlSessionFactory(); sqlSession = sqlSessionFactory.openSession(); EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); Map<Integer, Employee> map = mapper.getEmpsReturnMap(); System.out.println(map); } catch (IOException e) { e.printStackTrace(); }finally { sqlSession.close(); } } 8. resultMap自定义封装规则 ```xml 9. 联合查询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"> 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">
<resultMap id="myKey02" type="com.siki.bean.Key">
    <id property="id" column="id"/>
    <result property="keyName" column="keyName"/>
    <!--
        select: 指定一个查询sql的唯一标识,mybatis会自动调用指定的查询sql将查出的lock对象封装进来
        因为查询lock对象需要传递参数,所以我们需要传过去一个id
        column: 指定将哪一列的数据传过去
    -->
    <association property="lock" select="com.siki.dao.LockDao.getLockByIdSimple" column="lockId"/>
</resultMap>

<!--上面的级联查询太麻烦了,我们可以使用select属性指定分步查询-->
<select id="getKeyByIdSimple" resultMap="myKey02">
    select * from t_key where id = #{id}
</select>

<?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">

<resultMap id="myLock" type="com.siki.bean.Lock">
    <id property="id" column="id"/>
    <result property="lockName" column="lockName"/>
    <!--collection: 专门定义集合元素的封装-->
    <!--ofType: 指定集合里面元素的类型-->
    <collection property="keys" ofType="com.siki.bean.Key">
        <id property="id" column="kId"/>
        <result property="keyName" column="keyName"/>
    </collection>
</resultMap>

<select id="getLockById" resultMap="myLock">
    select l.id,l.lockName,k.id kId,k.keyName
    from t_lock l left join t_key k on k.lockId = l.id
    where l.id = #{id}
</select>


<select id="getLockByIdSimple" resultType="com.siki.bean.Lock">
    select * from t_lock where id = #{id}
</select>



```java
public class Key {

    private Integer id;

    private String keyName;

    private Lock lock;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getKeyName() {
        return keyName;
    }

    public void setKeyName(String keyName) {
        this.keyName = keyName;
    }

    public Lock getLock() {
        return lock;
    }

    public void setLock(Lock lock) {
        this.lock = lock;
    }

    @Override
    public String toString() {
        return "Key{" +
                "id=" + id +
                ", keyName='" + keyName + '\'' +
                ", lock=" + lock +
                '}';
    }
}


public class Lock {

    private Integer id;

    private String lockName;

    private List<Key> keys;  //一把锁对应好几把钥匙

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLockName() {
        return lockName;
    }

    public void setLockName(String lockName) {
        this.lockName = lockName;
    }

    public List<Key> getKeys() {
        return keys;
    }

    public void setKeys(List<Key> keys) {
        this.keys = keys;
    }

    @Override
    public String toString() {
        return "Lock{" +
                "id=" + id +
                ", lockName='" + lockName + '\'' +
                ", keys=" + keys +
                '}';
    }
}


public interface LockDao {

    //查询锁的时候将对应的所有钥匙也查出来
    public Lock getLockById(Integer id);

    public Lock getLockByIdSimple(Integer id);

}


public interface KeyDao {

    //将key和lock一起查出
    public Key getKeyById(Integer id);

    public Key getKeyByIdSimple(Integer id);

}


//级联属性封装结果
@Test
public void test08(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        KeyDao mapper = sqlSession.getMapper(KeyDao.class);
        Key key = mapper.getKeyById(2);
        System.out.println(key);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}

@Test
public void test09(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        LockDao mapper = sqlSession.getMapper(LockDao.class);
        Lock lock = mapper.getLockById(3);
        System.out.println(lock);
        List<Key> keys = lock.getKeys();
        System.out.println("所有的锁: ");
        for(Key key : keys){
            System.out.println(key);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}

//这种分步查询不管查什么每次都会查询两次,对数据库的性能消耗较大
//所以我们可以在全局配置文件中开启延迟加载: 需要的时候再去加载,不着急查询
@Test
public void test10(){
    SqlSession sqlSession = null;
    try {
        initSqlSessionFactory();
        sqlSession = sqlSessionFactory.openSession();
        KeyDao mapper = sqlSession.getMapper(KeyDao.class);
        Key key = mapper.getKeyByIdSimple(1);
        System.out.println(key.getKeyName());
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}

<a name="03384f97"></a>
## 5. 动态SQL

1. 动态 SQL是MyBatis强大特性之一,极大的简化我们拼装SQL的操作
2. 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似
3. MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作

   1. OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的<br />
表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL,SpEL等

      1. if
      2. choose (when, otherwise)
      3. trim (where, set)
      4. foreach

```xml
<!--mybatis-config.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mybatis/EmployeeDao.xml"/>
    </mappers>

</configuration>
<!--EmployeeDao.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.siki.dao.EmployeeDao">

    <!--抽取可重用的sql语句-->
    <sql id="mySql">select * from employee</sql>

    <select id="getEmpById" resultType="com.siki.bean.Employee">
        <include refid="mySql"/> where id = #{id}
    </select>

<!--    <select id="getEmpsByCondition" resultType="com.siki.bean.Employee">
        select * from employee
        &lt;!&ndash;where标签可以自动帮我们去掉前面的那个and&ndash;&gt;
        <where>
            &lt;!&ndash;test: 编写判断条件&ndash;&gt;
            <if test="id != null">
                and id > #{id}
            </if>
            &lt;!&ndash;and: &&  &quot;&quot;相当于""&ndash;&gt;
            <if test="name != null and name != &quot;&quot;">
                and name like #{name}
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
        </where>
    </select>-->

    <select id="getEmpsByCondition" resultType="com.siki.bean.Employee">
        <include refid="mySql"/>
        <!--
            trim: 截取字符串
                prefix: 为下面的sql整体添加一个前缀
                prefixOverrides: 取出整体字符串前面多余的字符
                suffix: 为整体添加后缀
                suffixOverrides: 去掉后面多余的字符
        -->
        <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
            <!--bind: 绑定一个表达式的值到一个变量中,了解即可-->
            <bind name="_name" value="'%' + name + '%'"/>
            <if test="id != null">
                and id > #{id}
            </if>
            <if test="name != null and name != &quot;&quot;">
                and name like #{_name} and
            </if>
            <if test="gender != null">
                gender = #{gender}
            </if>
        </trim>
    </select>

    <select id="getEmployeeByIdIn" resultType="com.siki.bean.Employee">
        <include refid="mySql"/> where id in
        <!--
            foreach: 帮我们遍历集合
                collection: 指定遍历的集合的key
                item: 给每次遍历出的元素起一个变量名,方便引用
                index: 索引
                    如果是一个List:
                        index指定的变量保存的是当前索引
                        item指定的变量就是当前遍历的元素的值
                    如果是一个Map:
                        index指定的变量保存的是当前遍历的元素的key
                        item指定的就是value
                open: 以什么开始
                close: 以什么结束
                separator: 每次遍历的元素之间的分隔符
        -->
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </select>


    <select id="getEmpsByConditionChoose" resultType="com.siki.bean.Employee">
        <include refid="mySql"/>
        <where>
            <!--相当于if..else-->
            <choose>
                <when test="id != null">id = #{id}</when>
                <when test="name != null and name != &quot;&quot;">name = #{name}                    </when>
                <otherwise>
                    1 = 1
                </otherwise>
            </choose>
        </where>
    </select>


    <select id="updateEmp">
        update employee
        <set>
            <if test="name != null and name != &quot;&quot;">name = #{name},</if>
            <if test="gender != null and gender != &quot;&quot;">gender = #{gender},</if>
            <if test="email != null and email != &quot;&quot;">email = #{email},</if>
            <if test="d_id != null and d_id != &quot;&quot;">d_id = #{d_id}</if>
        </set>
        <where>
            id = #{id}
        </where>
    </select>

</mapper>
public interface EmployeeDao {

    public Employee getEmpById(Integer id);

    public List<Employee> getEmpsByCondition(Employee employee);

    public List<Employee> getEmployeeByIdIn(@Param("ids") List<Integer> ids);

    public List<Employee> getEmpsByConditionChoose(Employee employee);

    public int updateEmp(Employee employee);

}


public class DynamicSQLTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void initSqlSessionFactory() throws IOException {
        String resource="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void test01(){
        SqlSession sqlSession = null;
        try {
            initSqlSessionFactory();
            sqlSession = sqlSessionFactory.openSession();
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = mapper.getEmpById(3);
            System.out.println(employee);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    @Test
    public void test02(){
        SqlSession sqlSession = null;
        try {
            initSqlSessionFactory();
            sqlSession = sqlSessionFactory.openSession();
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = new Employee();
            //employee.setId(1);
            employee.setName("k");
            List<Employee> emps = mapper.getEmpsByCondition(employee);
            System.out.println(emps);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    @Test
    public void test03(){
        SqlSession sqlSession = null;
        try {
            initSqlSessionFactory();
            sqlSession = sqlSessionFactory.openSession();
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
            List<Employee> list = mapper.getEmployeeByIdIn(Arrays.asList(1, 2, 3));
            System.out.println(list);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    @Test
    public void test04(){
        SqlSession sqlSession = null;
        try {
            initSqlSessionFactory();
            sqlSession = sqlSessionFactory.openSession();
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = new Employee();
            employee.setId(1);
            List<Employee> emps = mapper.getEmpsByConditionChoose(employee);
            System.out.println(emps);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    @Test
    public void test05(){
        SqlSession sqlSession = null;
        try {
            initSqlSessionFactory();
            sqlSession = sqlSessionFactory.openSession();
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = new Employee();
            employee.setId(1);
            employee.setName("xinxin");
            employee.setGender("0");
            employee.setEmail("1314520@qq.com");
            int i = mapper.updateEmp(employee);
            System.out.println(i);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

}

6. 缓存机制

  1. MyBatis 包含一个非常强大的查询缓存(底层是map)特性,它可以非常方便地配置和定制,缓存可以极大的提升查询效率

  2. MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

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

    1. 一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空
    2. 本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域
    3. 在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis.xml 中配置
  4. 二级缓存

    1. 二级缓存(second level cache),全局作用域缓存,二级缓存默认不开启,需要手动配置
    2. MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
    3. 二级缓存在 SqlSession 关闭或提交之后才会生效
    4. 使用步骤

      1. 全局配置文件中开启二级缓存
      2. 需要使用二级缓存的映射文件处使用cache配置缓存
      3. 注意:JavaBean需要实现Serializable接口
  5. 一级缓存和二级缓存联系

    1. 不会出现一级缓存和二级缓存中有同一个数据

      1. 二级缓存:一级缓存关闭了才有数据
      2. 一级缓存:会先去二级缓存中查看,如果没有,再看一级缓存,一级缓存没有的话,就去查数据库,数据库查询后的结果就放在一级缓存中
      3. 任何时候都是先看二级缓存,再看一级缓存,如果都没有才去查询数据库
  6. 缓存相关属性

    1. eviction=“FIFO”:缓存回收策略

      1. LRU – 最近最少使用的:移除最长时间不被使用的对象
      2. FIFO – 先进先出:按对象进入缓存的顺序来移除它们
      3. SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
      4. WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
      5. 默认的是 LRU
    2. flushInterval:刷新间隔,单位毫秒

      1. 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
    3. size:引用数目,正整数

      1. 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
    4. readOnly:只读,true/false

      1. true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势
      2. false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false
  7. 缓存有关设置

    1. 全局setting的cacheEnable

      1. 配置二级缓存的开关。一级缓存一直是打开的
    2. select标签的useCache属性

      1. 配置这个select是否使用二级缓存。一级缓存一直是使用的
    3. sql标签的flushCache属性

      1. 增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false
    4. sqlSession.clearCache()

      1. 只是用来清除一级缓存
    5. 当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear
<!--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>

    <settings>
        <!--开启全局缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mybatis/EmployeeDao.xml"/>
    </mappers>

</configuration>
<!--EmployeeDao.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.siki.dao.EmployeeDao">

    <!--使用二级缓存,什么都不用写-->
    <cache></cache>

    <select id="getEmpById" resultType="com.siki.bean.Employee">
        select * from employee where id = #{id}
    </select>

    <delete id="deleteById">
        delete from employee where id = #{id}
    </delete>

</mapper>
public class Employee implements Serializable {

    private Integer id;

    private String name;

    private String gender;

    private String email;

    private Integer d_id;

    public Employee() {

    }

    public Employee(Integer id, String name, String gender, String email, Integer d_id) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.email = email;
        this.d_id = d_id;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getD_id() {
        return d_id;
    }

    public void setD_id(Integer d_id) {
        this.d_id = d_id;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", email='" + email + '\'' +
                ", d_id=" + d_id +
                '}';
    }
}


public interface EmployeeDao {

    public Employee getEmpById(Integer id);

    public int deleteById(Integer id);

}


public class CacheTest {

    private SqlSessionFactory sqlSessionFactory;

    public void initSqlSessionFactory() throws IOException {
        String resource="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    //体验一级缓存
    @Test
    public void test01(){
        SqlSession sqlSession = null;
        try{
            initSqlSessionFactory();
            sqlSession = sqlSessionFactory.openSession();
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
            Employee employee1 = mapper.getEmpById(1);
            System.out.println(employee1);
            System.out.println("================================");
            Employee employee2 = mapper.getEmpById(1);
            System.out.println(employee2);
            System.out.println(employee1 == employee2);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    /*
        一级缓存失效的几种情况
            1. 不同的sqlSession,使用不同的一级缓存
            2. 同一个方法,不同的参数,由于之前没有查询过,所以还是会发新的sql
            3. 在sqlSession期间,执行了任何一次增删改操作,会把缓存清空
            4. 我们还可以手动清空缓存
                sqlSession.clearCache();
            每次查询,会先看一级缓存中有没有,如果没有就去发送新的sql,每个sqlSession拥有自己的一级缓存
     */
    @Test
    public void test02(){
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try{
            initSqlSessionFactory();
            //1. 第一个会话
            sqlSession1 = sqlSessionFactory.openSession();
            EmployeeDao mapper1 = sqlSession1.getMapper(EmployeeDao.class);
            Employee employee1 = mapper1.getEmpById(1);
            System.out.println(employee1);
            System.out.println("================================");
            //2. 第二个会话
            sqlSession2 = sqlSessionFactory.openSession();
            EmployeeDao mapper2 = sqlSession2.getMapper(EmployeeDao.class);
            Employee employee2 = mapper2.getEmpById(1);
            System.out.println(employee2);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession1.close();
            sqlSession2.close();
        }
    }

    @Test
    public void test03(){
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try{
            initSqlSessionFactory();
            //1. 第一个会话
            sqlSession1 = sqlSessionFactory.openSession();
            EmployeeDao mapper1 = sqlSession1.getMapper(EmployeeDao.class);
            Employee employee1 = mapper1.getEmpById(1);
            System.out.println(employee1);
            System.out.println("==============================");
            Employee employee2 = mapper1.getEmpById(2);
            System.out.println(employee2);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession1.close();
            sqlSession2.close();
        }
    }

    @Test
    public void test04(){
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try{
            initSqlSessionFactory();
            //1. 第一个会话
            sqlSession1 = sqlSessionFactory.openSession();
            EmployeeDao mapper1 = sqlSession1.getMapper(EmployeeDao.class);
            Employee employee1 = mapper1.getEmpById(1);
            System.out.println(employee1);
            System.out.println("================================");

            //执行增删改操作
            mapper1.deleteById(165);

            //2. 第二个会话
            sqlSession2 = sqlSessionFactory.openSession();
            EmployeeDao mapper2 = sqlSession2.getMapper(EmployeeDao.class);
            Employee employee2 = mapper2.getEmpById(1);
            System.out.println(employee2);
            sqlSession1.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession1.close();
            sqlSession2.close();
        }
    }

    /*
        体验二级缓存
     */
    @Test
    public void test05(){
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try{
            initSqlSessionFactory();
            sqlSession1 = sqlSessionFactory.openSession();
            sqlSession2 = sqlSessionFactory.openSession();
            EmployeeDao mapper1 = sqlSession1.getMapper(EmployeeDao.class);
            EmployeeDao mapper2 = sqlSession2.getMapper(EmployeeDao.class);
            Employee employee1 = mapper1.getEmpById(1);
            System.out.println(employee1);
            sqlSession1.close();
            System.out.println("============================");
            Employee employee2 = mapper2.getEmpById(1);
            System.out.println(employee2);
            sqlSession2.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

7. MyBatis逆向工程

<dependencies>
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.5</version>
    </dependency>
</dependencies>
<!--mbg.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--
        MyBatis3: 生成复杂的CRUD
        MyBatis3Simple: 生成简单的CRUD
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!--指导连接数据库-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="123456">
        </jdbcConnection>

        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!--生成JavaBean-->
        <javaModelGenerator targetPackage="com.siki.bean" targetProject=".\src">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!--SQL映射文件-->
        <sqlMapGenerator targetPackage="com.siki.dao"  targetProject=".\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!--生成Dao接口-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.siki.dao"  targetProject=".\src">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!--一个table指定一张表-->
        <table tableName="t_cat" domainObjectName="Cat" ></table>

    </context>
</generatorConfiguration>
public class MBGTest {

    @Test
    public void test() throws Exception{
        List<String> warnings = new ArrayList<>();
        boolean overwrite = true;
        File configFile = new File("mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration configuration = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator generator = new MyBatisGenerator(configuration, callback,warnings);
        generator.generate(null);
        System.out.println("ok~~~");
    }

}

MyBatis Plus

1. MyBatis Plus概述

  1. MyBatis Plus 是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
  2. 特性

    1. 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
    2. 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
    3. 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
    4. 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
    5. 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
    6. 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
    7. 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
    8. 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
    9. 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
    10. 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
    11. 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
    12. 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

2. 快速入门

  1. 导入对应的依赖
  2. 配置文件
  3. 编写代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.18</version>
</dependency>
<!--尽量不要同时导入mybatis和mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
//JavaBean
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Long id;

    private String name;

    private Integer age;

    private String email;

}


//在对应的Mapper上面继承基本的Mpper
@Repository
public interface UserMapper extends BaseMapper<User> {

    //继承之后,所有的CRUD已经全部完成,不需要像以前那样编写SQL映射文件

}


//mybatis-plus帮我们编写的类,实现了大部分的CRUD
public interface BaseMapper<T> {
    int insert(T var1);

    int deleteById(Serializable var1);

    int deleteByMap(@Param("cm") Map<String, Object> var1);

    int delete(@Param("ew") Wrapper<T> var1);

    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> var1);

    int updateById(@Param("et") T var1);

    int update(@Param("et") T var1, @Param("ew") Wrapper<T> var2);

    T selectById(Serializable var1);

    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> var1);

    List<T> selectByMap(@Param("cm") Map<String, Object> var1);

    T selectOne(@Param("ew") Wrapper<T> var1);

    Integer selectCount(@Param("ew") Wrapper<T> var1);

    List<T> selectList(@Param("ew") Wrapper<T> var1);

    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> var1);

    List<Object> selectObjs(@Param("ew") Wrapper<T> var1);

    IPage<T> selectPage(IPage<T> var1, @Param("ew") Wrapper<T> var2);

    IPage<Map<String, Object>> selectMapsPage(IPage<T> var1, @Param("ew") Wrapper<T> var2);
}


//主测试类
//扫描Mpper文件
@MapperScan("com.siki.mapper")
@SpringBootApplication
public class MybatisPlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }

}


//测试
@SpringBootTest
class MybatisPlusApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        //参数是一个Wapper条件构造器,这里我们先写null
        List<User> list = userMapper.selectList(null);
        for(User user : list){
            System.out.println(user);
        }
    }

}

注意:我们需要在主启动类上去扫描我们Mapper包下的所有接口

3. 配置日志

  1. 我们现在所有的sql都是不可见的,我们希望知道它是怎么执行的,所以需要配置日志
    #配置日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    

4. CRUD扩展

  1. insert插入
    //插入
    @Test
    public void testInsert(){
     User user = new User();
     user.setName("昕昕");
     user.setAge(18);
     user.setEmail("1314520@qq.com");
     //mybatis-plus会帮我们自动生成id
     int i = userMapper.insert(user);
     System.out.println(i);
     System.out.println(user);
    }
    
  1. 主键生成策略——>雪花算法

    1. 分布式系统唯一id生成方案:https://www.cnblogs.com/haoxinyue/p/5208136.html

    2. 雪花算法
      snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0

    3. 主键自增

      1. 实体类字段上标注@TableId,指定自增
      2. 数据库字段也一定要自增 ```java @Data @NoArgsConstructor @AllArgsConstructor public class User {

      //设置主键自增 @TableId(value = “id”,type = IdType.AUTO) private Long id;

      private String name;

      private Integer age;

      private String email;

}

//源码 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface TableId { String value() default “”;

IdType type() default IdType.NONE;   //默认不设置主键

}

public enum IdType { AUTO(0), //id自增 NONE(1), //未设置主键 INPUT(2), //手动输入,这个时候就需要自己输入id,否则为null ID_WORKER(3), //默认的全局id UUID(4), //全局唯一id ID_WORKER_STR(5); //全局id 字符串表示

private int key;

private IdType(int key) {
    this.key = key;
}

public int getKey() {
    return this.key;
}

}


   4. 
update更新
```java
//更新
@Test
public void testUpdate() {
    User user = new User();
    user.setId(1L);
    user.setName("昕昕");
    //通过条件自动拼接动态sql
    int i = userMapper.updateById(user);
    System.out.println(i);
}
  1. 自动填充

    1. 创建时间、修改时间,这些操作一般都是自动化完成的,我们不希望人为的手动进行更新

    2. 阿里巴巴开发手册:所有的数据库表,这些时间操作都需要配置,而且需要自动化

    3. 方式一:数据库级别(了解就行,工作中不允许修改数据库)

    4. 实体类同步

@Data @NoArgsConstructor @AllArgsConstructor

   //默认: ID_WORKER 全局唯一id
   @TableId(value = "id",type = IdType.AUTO)
   private Long id;

   private String name;

   private Integer age;

   private String email;

   private Date createTime;

   private Date updateTime;

![image-20220125091859921](C:\Users\浅唱初夏\AppData\Roaming\Typora\typora-user-images\image-20220125091859921.png)



5. 方式二:代码级别

```java
//第一步: 添加注解,实现添加操作时填充字段内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
//第二步: 编写处理器来处理这个注解即可
@Slf4j
@Component  //一定不要忘记把组件添加到容器中
public class MyMetaObjectHandler implements MetaObjectHandler {

    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill....");
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill....");
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}


//源码
public interface MetaObjectHandler {
    void insertFill(MetaObject var1);

    void updateFill(MetaObject var1);

    default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
        if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName)) {
            metaObject.setValue(fieldName, fieldVal);
        } else if (metaObject.hasGetter("et")) {
            Object et = metaObject.getValue("et");
            if (et != null) {
                MetaObject etMeta = SystemMetaObject.forObject(et);
                if (etMeta.hasSetter(fieldName)) {
                    etMeta.setValue(fieldName, fieldVal);
                }
            }
        }

        return this;
    }

    default Object getFieldValByName(String fieldName, MetaObject metaObject) {
        if (metaObject.hasGetter(fieldName)) {
            return metaObject.getValue(fieldName);
        } else {
            return metaObject.hasGetter("et." + fieldName) ? metaObject.getValue("et." + fieldName) : null;
        }
    }

    default boolean openInsertFill() {
        return true;
    }

    default boolean openUpdateFill() {
        return true;
    }
}


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface TableField {
    String value() default "";

    String el() default "";

    boolean exist() default true;

    String condition() default "";

    String update() default "";

    FieldStrategy strategy() default FieldStrategy.DEFAULT;

    FieldFill fill() default FieldFill.DEFAULT;

    boolean select() default true;
}


public enum FieldFill {
    DEFAULT,
    INSERT,
    UPDATE,
    INSERT_UPDATE;

    private FieldFill() {
    }
}
  3. 

执行插入操作,观察数据库

  4. 

执行修改操作,再次观察数据库

  1. 乐观锁
    它总是认为不会出现问题,无论干什么都不会去上锁,如果出现了问题,就会再次更新值测试

  2. 悲观锁
    它总是认为会出现问题,无论干什么都会去上锁,再去操作

  3. 当要更新一条记录的时候,希望这条记录没有被别人更新
    乐观锁实现方式:

      > - 取出记录时,获取当前version
    
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败
  1. 测试以下MP中的乐观锁插件
  2. 给数据库添加version字段,初始值为1

    1. 实体类加对应的字段
    2. 注册组件
    3. 测试
      @Version
      private Integer version;
      
    @EnableTransactionManagement  //开启事务管理
    @Configuration
    public class MyBatisConfig {
    
        //注册乐观锁插件
    @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor(){
         return new OptimisticLockerInterceptor();
        }
    
    }
    
    //测试乐观锁成功
    @Test
    public void testOptimisticLockerInterceptorSuccess(){
        User user = userMapper.selectById(2L);
        user.setName("昕昕宝贝");
    //修改
        userMapper.updateById(user);
    }
    

    image-20220125093736382

    image-20220125093818154 ```

    多线程情况下,测试乐观锁失败

//多线程情况下,测试乐观锁失败
@Test
public void testOptimisticLockerInterceptorFaild(){
    User user = userMapper.selectById(2L);
    user.setName("昕昕宝贝");

    //模拟另外一个线程执行了插队操作
    User user1 = userMapper.selectById(2L);
    user1.setName("xx");
    userMapper.updateById(user1);

    //这个时候再来修改就会失败
    userMapper.updateById(user);
}
  1. 查询操作

    @Test
    public void testSelect(){
     //查询单个用户
     User user = userMapper.selectById(1);
     System.out.println(user);
    
     //查询多个用户
     List<User> list = userMapper.selectBatchIds(Arrays.asList(1l, 2l, 3l, 4l));
     System.out.println(list);
    
     //按条件查询的方式一: map
     Map<String, Object> map = new HashMap<>();
     map.put("name","昕昕");
     map.put("age",3);
     List<User> users = userMapper.selectByMap(map);
     System.out.println(users);
    }
    
  1. 分页查询

    1. 配置分页插件

      @Bean
      public PaginationInterceptor paginationInterceptor(){
      return new PaginationInterceptor();
      }
      
    2. 测试

      @Test
      public void testPage(){
      //参数: 当前页数  分页的页数大小
      Page<User> page = new Page<>(1,3);
      userMapper.selectPage(page,null);
      List<User> list = page.getRecords(); //进行封装
      for(User user : list){
        System.out.println(user);
      }
      System.out.println(page.getTotal());  //总数据量
      System.out.println(page.getPages());  //页数
      }
      
  2. delete删除

    @Test
    public void testDelete(){
     //根据id删除
     int i = userMapper.deleteById(5l);
     System.out.println(i);
     //批量删除
     int j = userMapper.deleteBatchIds(Arrays.asList(1364025384642846723L, 1364025384642846724L));
     System.out.println(j);
    }
    
  1. 逻辑删除 ```java /* 物理删除: 从数据库中直接移除 逻辑删除: 在数据库中没有被移除,而是通过一个变量来让它失效 例如: 管理员可以查看被删除的记录、回收站

测试:

1. 在数据库中添加deleted字段
2. 实体类中添加属性
3. 配置组件以及配置文件

*/ @TableLogic //逻辑删除 private Integer deleted;

//逻辑删除组件 @Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); }

//配置逻辑删除 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0

//测试删除 @Test public void testLogic(){ int i = userMapper.deleteById(1l); System.out.println(i); }


<br />
<br />虽然没有删除,但是此时查询是查询不到的
<br />再次查询的时候会自动过滤掉实现逻辑删除的字段
<br />


<a name="f62c2bc3"></a>
## 5. 性能分析插件

作用:性能分析拦截器,用于输出每条SQL语句及其执行时间

1. 导入插件
2. 测试

```properties
#设置开发环境
spring.profiles.active=dev
//性能分析插件
@Bean
@Profile({"dev","test"})  //设置只有开发环境和测试环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor(){
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    //设置sql执行的最大时间为1ms,如果超过了就不执行
    performanceInterceptor.setMaxTime(1);
    //sql格式化开启
    performanceInterceptor.setFormat(true);
    return performanceInterceptor;
}


@Test
void contextLoads() {
    List<User> list = userMapper.selectList(null);
    for(User user : list){
        System.out.println(user);
    }
}

6. 条件查询器Wrapper

我们写一些复杂的sql语句的时候,就可以使用它来代替,十分重要

@SpringBootTest
public class WrapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test01(){
        //查询name不为null、email不为null、age >= 20的user
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.isNotNull("name").isNotNull("email").ge("age",20);
        List<User> list = userMapper.selectList(wrapper);
        for(User user : list){
            System.out.println(user);
        }
    }

    @Test
    public void test02(){
        //查询单个用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","Tom");
        User user = userMapper.selectOne(wrapper);
        System.out.println(user);
    }

    @Test
    public void test03(){
        //查询age在20-23之间的
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age",20,23);
        Integer count = userMapper.selectCount(wrapper);
        System.out.println(count);
    }

    @Test
    public void test04(){
        //模糊查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //左右表示"%"在左还是右
        wrapper.notLike("name","x").likeRight("name","T");
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);  //遍历
    }

    @Test
    public void test05(){
        //子查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.inSql("id","select id from user where id < 3");
        List<Object> objects = userMapper.selectObjs(wrapper);
        for(Object obj : objects){
            System.out.println((User)obj);
        }
    }

    @Test
    public void test06(){
        //通过id进行排序
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

}

7. 代码自动生成器

//代码自动生成器
public class Code {

    public static void main(String[] args) {
        //1. 构建一个代码自动生成器对象
        AutoGenerator generator = new AutoGenerator();
        //2. 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");  //获取项目路径
        gc.setOutputDir(projectPath + "/src/main/java");  //设置输出路径
        gc.setAuthor("昕昕");  //设置作者名字
        gc.setOpen(false);  //设置项目启动后是否打开windows文件夹
        gc.setFileOverride(false);  //文件是否覆盖
        gc.setServiceName("%sService");  //设置Service名称   %s: 表示不以I开头
        gc.setIdType(IdType.ID_WORKER);  //默认全局唯一id
        gc.setSwagger2(true);
        generator.setGlobalConfig(gc);
        //3. 设置数据源
        DataSourceConfig dc = new DataSourceConfig();
        dc.setUsername("root");
        dc.setPassword("123456");
        dc.setUrl("jdbc:mysql://localhost:3306/mybatisplus");
        dc.setDriverName("com.mysql.jdbc.Driver");
        generator.setDataSource(dc);
        //4. 配置包
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("blog");
        pc.setParent("com.siki");
        pc.setEntity("bean");
        pc.setController("controller");
        pc.setService("service");
        pc.setMapper("mapper");
        generator.setPackageInfo(pc);
        //5. 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("user");  //设置要映射的表名
        strategy.setNaming(NamingStrategy.underline_to_camel); //支持驼峰命名
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setLogicDeleteFieldName("deleted");  //设置逻辑删除的字段
        //6. 自动填充配置
        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
        TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
        List<TableFill> list = new ArrayList<>();
        list.add(gmtCreate);
        list.add(gmtUpdate);
        strategy.setTableFillList(list);
        //7. 乐观锁配置
        strategy.setVersionFieldName("version");
        strategy.setControllerMappingHyphenStyle(true);
        generator.setStrategy(strategy);
        //最后的执行
        generator.execute();
    }

}