Mybatis笔记(3)-基本crud、参数映射以及动态sql

基本CRUD以及参数映射

#{}和${}

默认情况下,使用 #{} 时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。

PreparedStatement 的占位符不能用于一些元数据字段,如表名、列名。这时可以使用${},mybatis会直接插入一个不转义的字符串。

如要查询某个字段名为指定值的记录,不用在mapper中定义多个查询方法,如:findById()findByUsername()。可以直接用一个方法:其中columnName指字段名,value指字段值

User selectByColumn(@Param("columnName") String columnName, @Param("value") String value);

映射文件中的sql语句:

<select id="selectByColumn" resultType="user">
    select * from user where ${columnName}=#{value}
</select>
  • 其中columnName将直接替换${columnName}

一般优先使用#{},其使用的是PreparedStatement会更加安全。而${}可能会引起SQL注入


参数映射

  • 对于方法形参到sql语句的参数映射

    • 当Mapper方法只有一个参数时,#{}内的值无需与方法形参名对应,可以任意指定。若是有多个形参:
      • 以param加上形参在参数列表中的位置来命名(从1开始),比如:#{param1}、#{param2}
      • 使用@Param注解修饰形参,通过其value属性自定义命名,并在#{}内使用对应命名。
    • 当传入的参数的一个JavaBean,要获取其属性,通过#{属性名}即可。(若是bean中含bean,则使用#{innerBean.paramName}的形式
    • parameterType属性可以指定传入的参数类型。该属性是可选的,一般情况下mybatis都可以推断出具体传入语句的参数类型。
  • 对于sql语句到返回值的参数映射

    • 通过resultType属性可以指定返回值类型。且当返回值是集合时,返回类型不是设置为集合,而是应指定其集合元素的类型。resultType适用于表的字段名与JavaBean的成员变量名一一对应的情况,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。若是不对应将会抛出异常。这种情况可以使用<resultMap>标签。

      PS:当使用#{}从传入的对象中获取其属性值时是区分大小写的,如User对象有一个userName属性,若使用#{username},Mybatis将会找不到userName属性,抛出异常。

    • <resultMap>标签可以对表字段与JavaBean属性不对应的情况进行手动映射。且手动映射与自动映射会相互配合即在<resultMapL>中可以只手动映射不对应的字段,那些对应的字段可以省略,Mybatis会进行自动映射处理<resultMap>配置完毕后,在<select> 等标签中通过resultMap属性引用。

    • 返回类型的配置不能省略

<resultMap>示例:

Book类:

public class Book {
    private Integer id;
    private String bookName;
    private String bookAuthor;
    private Integer price;
    //getter、setter、toString
}

book表:

image-20200915215129913

可看到Book类的bookName、bookAuthor属性与book表的book_name,author不对应。


映射文件的配置:

  • <resultMap>的id属性唯一标识该映射
  • type属性指定要映射的JavaBean类;id标签用于映射主键,result标签用于映射普通字段。property属性指定JavaBean的属性名,column属性指定表的字段名
  • 在select标签中使用resultMap="bookMap"引用配置的映射。
<resultMap id="bookMap" type="book">
    <id property="bookName" column="book_name" />
    <result property="bookAuthor" column="author" />
</resultMap>

<select id="selectById" resultMap="bookMap">
    select * from book where id=#{id}
</select>

测试方法:

@Test
public void test() throws IOException {
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = builder.build(stream);
    SqlSession session = factory.openSession();

    BookMapper mapper = session.getMapper(BookMapper.class);
    Book book = mapper.selectById(1);
    System.out.println(book);
}
//输出:Book(id=1, bookName=Java疯狂讲义, bookAuthor=李刚, price=100)

select ★

下文例子中的user表:其中,id是自增主键

image-20200915181249271

User类:

public class User {
    private Integer id;
    private String username;
    private String password;
    //...set、getter,toString
}

----------------

直接上例子:

UserMapper中查询相关方法定义:

//根据id查询
User selectById(Integer id);

//查询全部,返回类型是集合
List<User> selectAll();

//模糊查询。查询username中带有keyword的用户
List<User> selectByFuzzy(String keyword);

//根据指定字段和其字段值查询
User selectByColumn(String columnName, String value);

对应的映射文件:

<select id="selectById" resultType="user" >
    select * from user where username = #{id}
</select>

<select id="selectAll" resultType="user">
    select * from user
</select>

<select id="selectByFuzzy" resultType="user">
    select * from user where username like #{keyword}
</select>

<select id="selectByColumn" resultType="user">
    select * from user where ${param1}=#{param2}
</select>


要注意的点

  • select标签中的id属性指定的是Mapper中的方法名。(对后文的<insert>等标签一样)
  • resultType属性指定返回值类型。例子中的user指User类,已使用别名。
  • 注意selectAll方法的返回值的List集合,而对应select标签的resultType属性是List的元素User。
  • selectByColumn是根据字段名跟字段值进行查询。与上文例子相同。该mapper方法有两个参数,在sql语句中使用param+形参位置的形式映射参数
  • 在测试方法中,调用mapper的selectByFuzzy方法时,传入的参数是%小%即进行模糊查询时,要注意传参时先拼接好通配符


测试方法:

测试类中使用了Junit的@Before初始化了SqlSessionFactory,并作为测试类的成员变量:

private SqlSessionFactory factory;

@Before
public void initFactory() throws IOException {
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
    factory = builder.build(in);
}
@Test
public void testSelect(){
    try(SqlSession session = factory.openSession();){
        UserMapper mapper = session.getMapper(UserMapper.class);

        User user = mapper.selectById(2);
        System.out.println("id为2的User: " + user);

        List<User> users = mapper.selectAll();
        System.out.println("所有User: ");
        for (User u : users){
            System.out.println(u);
        }

        //查询用户名中带有‘小’的用户
        List<User> users2 = mapper.selectByFuzzy("%小%");
        System.out.println("用户名中带有 小 的用户:");
        for(User u : users2){
            System.out.println(u);
        }

        User user1 = mapper.selectByColumn("username", "张三");
        System.out.println("查询username字段为张三的记录:" + user1);
    }
}
/**
输出:
id为2的User: User(id=2, username=李四, password=5896596)
所有User: 
User(id=1, username=张三, password=123456)
User(id=2, username=李四, password=5896596)
User(id=3, username=王小强, password=666666)
User(id=4, username=许小红, password=777777)
用户名中带有 小 的用户:
User(id=3, username=王小强, password=666666)
User(id=4, username=许小红, password=777777)
查询username字段为张三的记录:User(id=1, username=张三, password=123456)
*/

insert ★

同上,直接看例子。

Mapper方法定义:

void insert(User user);

对应的映射文件配置:

<insert id="insert" parameterType="user" useGeneratedKeys="true" keyProperty="id">
    insert into user values(null, #{username}, #{password})
</insert>

测试方法:

@Test
public void testInsert(){
    try(SqlSession sqlSession=factory.openSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsername("蜘蛛侠");
        user.setPassword("555969");
        mapper.insert(user);

        System.out.println("执行插入后,获取user的id值:" + user.getId());
        //提交事务
        sqlSession.commit();
    }
}
//输出:执行插入后,获取user的id值:11


要注意的点

  • 注意到sql语句并没有插入id值(id是自增主键),可以通过useGeneratedKeys="true" keyProperty="id"的配置,获取执行插入操作后新记录的主键值,获取的主键值会返回给传入的JavaBean;keyProperty指定的是表的字段名,,如例子中执行插入操作后,把插入记录的id值赋值给user对象。

    • 也可以通过<selectKey>标签实现:(order属性值表示插入后或插入前获取主键)

      <insert id="insert" parameterType="user">
          <selectKey keyProperty="id" order="AFTER" resultType="int">
              select LAST_INSERT_ID();
          </selectKey>
          insert into user values(null, #{username}, #{password})
      </insert>
      
  • 在测试方法的最后,有一句sqlSession.commit();mybatis默认开启事务,即autoCommit属性为false,所以执行插入操作后需要提交事务。也可以在获取sqlSession时指定autoCommit属性为true,如下:

    SqlSession sqlSession=factory.openSession(true)(更新、删除操作也一样)

    否则操作将会回滚。


delete & update

这两个元素与insert大同小异。看个简单示例即可。

Mapper方法:

void update(User user);

void deleteById(Integer id);

映射文件:

<update id="update">
    update user set username=#{username}, password=#{password} where id = #{id}
</update>

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


测试方法:

@Test
public void testUpdate(){
    try(SqlSession session = factory.openSession(true)){
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = new User();
        user.setId(3);
        user.setUsername("王宝强");
        user.setPassword("666888");

        mapper.update(user);
        System.out.println("修改了id为3的user");
    }
}

@Test
public void testDelete(){
    try(SqlSession session = factory.openSession(true)){
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.deleteById(14);
        System.out.println("删除了id为14的user");
    }
}

抽取可复用sql语句

通过<sql>标签定义可重用的sql语句片段,通过<include>标签引用。

官方文档的示例:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>


动态SQL

Mybatis的动态sql可以使用简单的配置来实现根据条件拼接sql语句。让开发者可以不用过多关注于语法方面的问题。

下文例子中的数据表(employee):

image-20200916131813006

对应的JavaBean:

public class Employee {
    private Integer id;
    private String name;
    private String gender;
    private String department;
    private Integer salary;
    //...set getter toString
}

if

EmployeeMapper中的方法:

List<Employee> selectByConditionIf(Employee employee);

对应的映射文件:

<select id="selectByConditionIf" resultType="employee">
    select * from employee where gender=#{gender}
    <if test="name != null">and name like #{name}</if>
    <if test="salary != null">and salary>#{salary}</if>
</select>
  • test属性设置了条件。当employee的相关属性不为空时,将会在where中添加对应条件。test属性中要使用传入参数的属性时,直接使用属性名即可。

  • where关键字后必须带有一个条件,否则当所有if条件不满足时,sql语句将会出现语法错误:select * from employee where

  • 若是test中的条件要用到与或非,不能使用&& ||等符号,应使用and not or。如author != null and author.name != null

  • 当所有条件都不满足,就只有gender=#{gender}一个条件

测试方法:

@Test
public void testIf(){
    try(SqlSession session = factory.openSession()){
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

        Employee employee = new Employee();
        employee.setGender("男");
        employee.setName("%小%");
        employee.setSalary(1000);

        List<Employee> employees = mapper.selectByConditionIf(employee);

        for(Employee e : employees){
            System.out.println(e);
        }
    }
}
//输出:
//Employee(id=1, name=小明, gender=男, department=保安部, salary=5000)
//Employee(id=3, name=小强, gender=男, department=保安部, salary=5000)

choose

<choose> 类似于Java的switch,即按顺序判断条件,若满足则拼接对应的语句,之后的条件不再判断。所有条件都不满时,则拼接<otherwise>中的语句

示例:

EmployeeMapper中的方法:

List<Employee> selectByConditionChoose(Employee employee);

对应的映射文件:

<select id="selectByConditionChoose" resultType="employee">
    select * from employee where 1=1
    <choose>
        <when test="name != null">and name like #{name}</when>
        <when test="salary != null">and salary>#{salary}</when>
        <otherwise>
            and gender=#{gender}
        </otherwise>
    </choose>
</select>

where

相比于使用if,使用<where>标签在sql语句中不用出现where关键字。where元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句若子句的开头为 “AND” 或 “OR”,where元素也会将它们去除

示例:

Mapper方法:

List<Employee> selectByConditionWhere(Employee employee);

映射文件:

<select id="selectByConditionWhere" resultType="employee">
    select * from employee
     <where>
         <if test="gender != null">and  gender=#{gender}</if>
         <if test="name != null">and name like #{name}</if>
         <if test="salary != null">and salary>#{salary}</if>
     </where>
</select>

当有条件满足时,mybatis会插入对应where子句。若没有条件满足,则会查询所有记录。


set

<where>类似,可通过<set> 实现动态更新语句。

示例:

Mapper方法:

void updateByCondition(Employee employee);

映射文件:

<update id="updateByCondition">
    update employee
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="gender != null">gender = #{gender},</if>
        <if test="department != null">department = #{department},</if>
        <if test="salary != null">salary = #{salary}</if>
    </set>
    where id = #{id}
</update>

测试方法:

@Test
public void testUpdateByCondition(){
    //注意以开启事务的方式获取SqlSession
    try(SqlSession session = factory.openSession(true)){
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

        Employee employee = new Employee();
        employee.setId(1);
        employee.setDepartment("程序员");
        employee.setSalary(15000);

        mapper.updateByCondition(employee);
    }
}

执行测试方法,id为1的记录的department字段和salary字段被更新。

  • 注意if元素里的sql语句尾部的逗号不可少(最后一个if可省略逗号),否则会发送语法错误。
  • 当所有的if都不满足条件会发生语法错误

foreach

<foreach> 可以通过循环的方式获取集合中的值。尤其适合IN语句。

根据id集合查询的示例:

Mapper方法:

List<Employee> selectByIdsWithForeach(List<Integer> ids);

映射文件:

<select id="selectByIdsWithForeach" resultType="employee">
    select * from employee
    where id in
    <foreach collection="collection" index="index" item="id"
             open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

测试方法:

@Test
public void testSelectByIdsWithForeach(){
    try(SqlSession session = factory.openSession()){
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

        ArrayList<Integer> ids = new ArrayList<>();
        //获取id为1、2、3的记录
        ids.add(1);
        ids.add(2);
        ids.add(3);

        List<Employee> employees = mapper.selectByIdsWithForeach(ids);
        for(Employee e : employees){
            System.out.println(e);
        }
    }
}

<foreach> 的collection属性的属性值只能为listcollection,使用别的命名比如ids会抛出以下异常:

Cause: org.apache.ibatis.binding.BindingException: Parameter 'ids' not found. Available parameters are [collection, list]

从异常来看应该是只能使用这两种命名....

且Mapper方法的集合形参的参数名不用跟collection属性值相对应

posted @ 2020-09-18 15:50  bxxiao  阅读(376)  评论(0)    收藏  举报