凌余阵兮躐余行,左骖殪兮右刃伤。

Mybatis Zero

查询

Mybatispuls查询指定字段的方式

        LambdaQueryWrapper<BrandEntity> brandWrapper = new LambdaQueryWrapper<>();
        brandWrapper.select(BrandEntity::getName).eq(BrandEntity::getBrandId, categoryBrandRelation.getBrandId());
        BrandEntity brand = brandService.getOne(brandWrapper);

        log.info("brand: {}", brand);
brand: BrandEntity(brandId=null, name=华为, logo=null, descript=null, showStatus=null, sort=null, firstLetter=null)

插入前填充字段—selectKey 标签

说明

对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。

​ 这里有一个简单(也很傻)的示例,它可以生成一个随机 ID(不建议实际使用,这里只是为了展示 MyBatis 处理问题的灵活性和宽容度):

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

在上面的示例中,首先会运行 selectKey 元素中的语句,并设置 Author 的 id,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。

selectKey 元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">
属性 描述
keyProperty selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
resultType 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
order 可以设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType 和前面一样,MyBatis 支持 STATEMENTPREPAREDCALLABLE 类型的映射语句,分别代表 Statement, PreparedStatementCallableStatement 类型。

案例

image-20231114095553118

对应关系

注意点

占位符(#、$)

  1. #{} 参数占位符

#占位符的特点

  1. #{}是 sql 的参数占位符
  2. MyBatis处理 #{ } 占位符,使用的 JDBC 对象是 PreparedStatement 对象,执行sql语句的效率更高。
  3. 使用 PreparedStatement 对象,能够避免 sql 注入,使得sql语句的执行更加安全。
  4. #{ } 常常作为列值使用,位于sql语句中等号的右侧;#{ } 位置的值与数据类型是相关的。

使用 #{ } 对数据库执行 update 操作

package com.bjpowernode.dao;
 
import com.bjpowernode.entity.Student;
 
public interface StudentDao {
 
    //更新学生信息
    int updateStudent(Student student);
 
}
<!-- 更新学生信息 -->
<update id="updateStudent">
    update student set name=#{name},email=#{email} where id=#{id}
</update>
    @Test
    public void testUpdateStudent() {
        SqlSession session=MyBatisUtil.getSqlSession();
        StudentDao studentDao=session.getMapper(StudentDao.class);
        Student student=new Student();
        student.setId(1003);
        student.setName("最强王者");
        student.setEmail("123456@qq.com");
        student.setAge(28);
        int rows=studentDao.updateStudent(student);
        session.commit();
        System.out.println("更新学生的rows === " + rows);
        session.close();
    }

img

img


  1. $占位符

$占位符的特点

  1. ${}是 Properties 文件中的变量占位符
  2. MyBatis处理 ${ } 占位符,使用的 JDBC 对象是 Statement 对象,执行sql语句的效率相对于 #{ } 占位符要更低。
  3. ${ } 占位符的值,使用的是字符串连接的方式,有 sql 注入的风险,同时也存在代码安全的问题。
  4. ${ } 占位符中的数据是原模原样的,不会区分数据类型。
  5. ${} 占位符常用作表名或列名,这里推荐在能保证数据安全的情况下使用 ${ }。

使用 ${ } 对数据库执行 select 操作

package com.bjpowernode.dao;
 
import com.bjpowernode.entity.Student;
import org.apache.ibatis.annotations.Param;
 
import java.util.List;
 
public interface StudentDao {
 
    //${}占位符的使用,使用@Param命名参数
    List<Student> queryStudent(@Param("studentName") String name);
 
}
<!-- ${}占位符的使用 -->
<select id="queryStudent" resultType="com.bjpowernode.entity.Student">
    select * from student where name=${studentName}
</select>
    @Test
    public void testQueryStudent() {
        SqlSession session= MyBatisUtil.getSqlSession();
        StudentDao studentDao=session.getMapper(StudentDao.class);
        List<Student> students=studentDao.queryStudent("'张起灵'");
        for(Student stu : students) {
            System.out.println("stu === " + stu);
        }
        session.close();
    }

这里注意代码中通过 ${ } 占位符传值的地方,如果我们写成下面这样,代码运行一定是会报错的!!!

List<Student> students=studentDao.queryStudent("张起灵");

这是因为 ${ } 占位符中的数据是原模原样的,不会区分数据类型。也就是说它会把你的mapper文件中的sql语句转换为:

select * from student where name=张起灵  (错误!!!)

对sql语句有了解的人肯定都能看出这里的错误,首先我们的name字段的数据类型是 varchar,而这里 name=张起灵 显然是错误的,应该是 name= ‘张起灵’ 这样才对,所以代码中通过 ${ } 占位符传值的地方,应该这样写:👇👇👇

List<Student> students=studentDao.queryStudent("'张起灵'");

对参数 张起灵 添加引号,根据 ${ } 占位符中的数据是原模原样的,此时它会把你的mapper文件中的sql语句转换为:

select * from student where name='张起灵'  (正确!!!)

img


  1. #{ }、${ } 占位符的综合使用

上面大致介绍了一下这两种占位符的使用规则和语法,下面再用一个综合的例子来应用一下这两种占位符吧!!!

package com.bjpowernode.dao;
 
import com.bjpowernode.entity.Student;
import org.apache.ibatis.annotations.Param;
 
import java.util.List;
 
public interface StudentDao {
 
    List<Student> queryStudentOrderByColName(@Param("myName") String myName,
                                             @Param("colName") String colName,
                                             @Param("tableName") String tableName);
}
<!-- 按照某个列排序 -->
<select id="queryStudentOrderByColName" resultType="com.bjpowernode.entity.Student">
    select * from ${tableName} where name=#{myName} order by ${colName} desc
</select>
    @Test
    public void testQueryStudentOrderByColName() {
        SqlSession session= MyBatisUtil.getSqlSession();
        StudentDao studentDao=session.getMapper(StudentDao.class);
        List<Student> students=studentDao.queryStudentOrderByColName("张起灵","id","student");
        for(Student stu : students) {
            System.out.println("stu === " + stu);
        }
        session.close();
    }

上述sql语句的意思是:从 student 表中查询 name=”张起灵” 的所有字段内容,并把结果按照 id 列进行降序排序。

img

  1. ${} 占位符的 SQL 注入
    <select id="getByName" resultType="com.pika.entity.Balance">
        select *
        from t_balance
        where name like ${name}
    </select>

image-20230513125907853

image-20230513125930932

也就是说,无论 name 值是否存在, 由于 or 1 永远成立,会查询出数据库表中所有数据。

其它

lambda获取属性名,源码解析

最近项目中使用mybatisplus 作为项目的orm,效率比mybatis提升了不少,用起来相当方便,其中通过lambda表达式取得字段名,特别方便

        LambdaQueryWrapper<SpClips> where = new LambdaQueryWrapper<>();
        where.in(SpClips::getClipId, ids);

这是怎么实现的呢,带着好奇心我们来分析一下这块的源码。 首先我们看下 AbstractLambdaWrapper 抽象类

    protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
        return getColumn(LambdaUtils.resolve(column), onlyColumn);
    }

columnToString 函数将lambda表达式转换成字段名, 我们再看下 LambdaUtils.resolve(column) 这行代码处理的逻辑

    /**
     * 解析 lambda 表达式, 该方法只是调用了 {@link SerializedLambda#resolve(SFunction)} 中的方法,在此基础上加了缓存。
     * 该缓存可能会在任意不定的时间被清除
     *
     * @param func 需要解析的 lambda 对象
     * @param <T>  类型,被调用的 Function 对象的目标类型
     * @return 返回解析后的结果
     * @see SerializedLambda#resolve(SFunction)
     */
    public static <T> SerializedLambda resolve(SFunction<T, ?> func) {
        Class<?> clazz = func.getClass();
        return Optional.ofNullable(FUNC_CACHE.get(clazz))
            .map(WeakReference::get)
            .orElseGet(() -> {
                SerializedLambda lambda = SerializedLambda.resolve(func);
                FUNC_CACHE.put(clazz, new WeakReference<>(lambda));
                return lambda;
            });
    }

该段代码将lambda表达式转换成 SerializedLambda 对象,我们在来看看SerializedLambda 有哪些信息

    private Class<?> capturingClass;
    private String functionalInterfaceClass;
    private String functionalInterfaceMethodName;
    private String functionalInterfaceMethodSignature;
    private String implClass;
    private String implMethodName;
    private String implMethodSignature;
    private int implMethodKind;
    private String instantiatedMethodType;
    private Object[] capturedArgs;

这里面有方法名信息,到目前为止,实现思路就比较清楚了,

在这里插入图片描述 初始化类:TableInfoHelper 核心类:LambdaUtils 、SerializedLambda

版权声明:如无特别声明,本站收集的文章归  HuaJi66/Others  所有。 如有侵权,请联系删除。

联系邮箱: GenshinTimeStamp@outlook.com

本文标题:《 MyBatis-Plus Zero 学习笔记 》

本文链接:/mybatis/MybatisPlus-Zero.html