03-Mybatis 映射文件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/my_momo_csdn/article/details/91045404

映射文件

  • mybatis的查询sql和参数结果集映射的配置都是在映射文件中配置。相关的配置很多,这里我们从出参和入参2个角度先写一部分

一、入参

1.1 #和$

  • 使用JDBC我们都知道,jdbc传参时,一种是使用Statement,还有一种是使用PreparedStatement。前者有SQL注入的潜在隐患,在MyBatis中,传递单个参数有两种方式,一种是使用#,还有一种是使用KaTeX parse error: Expected 'EOF', got '#' at position 4: ,其中#̲对应了Jdbc种的Prepar…则对应了Jdbc种的Statement,因此在MyBatis种,推荐使用#。

1.1.1 案例

  • 我的数据库存在如下记录:(select * from tb_player;)
id playName playNo team
1 Kobe Brayent 24 laker
2 Lebron James 23 laker
3 Tim Duncan 21 spurs
4 leonard 2 raptors
5 Stephen Curry 30 warriors
6 Klay Thompson 11 warriors

1.1.2 测试代码

public class Test04 {

    //数据库相关信息
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://192.168.11.27:3306/demo?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true";
    static final String USER = "root";
    static final String PASS = "introcks1234";

    static Statement stmt = null;
    static PreparedStatement preparedStatement = null;
    static Connection conn = null;

    @Test
    public void test() {
        String nameRight = "Lebron James"; //模拟用户输入正确的名称时候的查询
        String fakeName = "Lebron Jamesxxx' or '1 = 1 "; //模拟用户输入错误的名称时候的查询
        int resultOfRightNameStatement = searchByName(nameRight, false); //使用Statement查询正确的名字
        int resultOfFakeNameStatement = searchByName(fakeName, false); //使用Statement查询错误的名字
        int resultOfRightNamePs = searchByName(nameRight, true); //使用PreparedStatement查询正确的名字
        int resultOfFakeNamePs = searchByName(fakeName, true); //使用PreparedStatement查询错误的名字
        //打印结果,通过观察查询到多少记录,来判断是否有SQL注入
        System.out.println("使用Statement查询正确的sql, 查询总数为:" + resultOfRightNameStatement);
        System.out.println("使用Statement查询错误的sql,查询总数为:" + resultOfFakeNameStatement);
        System.out.println("使用PreparedStatement查询正确的sql, 查询总数为:" + resultOfRightNamePs);
        System.out.println("使用PreparedStatement查询错误的sql,查询总数为:" + resultOfFakeNamePs);
    }

    public static int searchByName(String username, boolean safe) {
        int count = 0;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
            String sql;
            ResultSet rs = null;
            if (safe) {
                //安全的查询,则使用PreparedStatement
                sql = "SELECT * FROM tb_player where playName= ?";
                PreparedStatement preparedStatement = conn.prepareStatement(sql);
                preparedStatement.setString(1, username);
                System.out.println("打印sql:" + preparedStatement.toString());
                rs = preparedStatement.executeQuery();
            } else {
                //不安全的查询,使用Statement
                sql = "SELECT * FROM tb_player where playName='" + username + "'";
                Statement statement = conn.createStatement();
                System.out.println("打印sql:" + sql);
                rs = statement.executeQuery(sql);
            }
            if (rs != null) {
                while (rs.next()) {
                    //计算查询到的结果总数
                    count++;
                }
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            try {
                if (stmt != null)
                    stmt.close();
            } catch (SQLException se2) {
            } 
            try {
                if (conn != null)
                    conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
        return -1;
    }
}


结果:
打印sql:SELECT * FROM tb_player where playName='Lebron James'
打印sql:SELECT * FROM tb_player where playName='Lebron Jamesxxx' or '1 = 1 '
打印sql:com.mysql.jdbc.JDBC4PreparedStatement@27abe2cd: SELECT * FROM tb_player where playName= 'Lebron James'
打印sql:com.mysql.jdbc.JDBC4PreparedStatement@60215eee: SELECT * FROM tb_player where playName= 'Lebron Jamesxxx\' or \'1 = 1 '
使用Statement查询正确的sql, 查询总数为:1
使用Statement查询错误的sql,查询总数为:6
使用PreparedStatement查询正确的sql, 查询总数为:1
使用PreparedStatement查询错误的sql,查询总数为:0

1.1.3 结论

  • 我们看到,当使用Statement的时候,我们通过构造非法参数fakeName = "Lebron Jamesxxx’ or '1 = 1 "达到了SQL注入,因为我们查到了全部的记录
  • 使用PreparedStatement,输入错误的sql我们是查询不到任何记录的
  • 我们通过打印出来的预编译语句,我们也可以看到为什么PreparedStatement可以防止SQL注入,因为它把输入中的单引号加了转义字符,因此底层查询的
    时候,我们输入里面的’1=1’变成了条件的一部分,自然查不到,但是对于Statement,他却把’1=1’当做了一个逻辑或的条件,导致总条件永远为true,因此查到了全部的记录,这也是PreparedStatement底层防止sql注入的原理,毫无疑问,我们推荐使用#方式。

1.2 多个参数

  • 当有多个参数的时候,传参方式有map(不建议使用)/注解(小于5个时使用)/javaBean(大于5个时使用)。

1.2.1 Map

  • 不直观,不建议使用

1.2.2 javaBean

  • 参数较多时使用

1.2.3 注解

  • 参数较少时使用

1.2.4 代码

  • 映射文件:
<?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.intellif.mozping.dao.PeopleMapper">


    <insert id="addPeople" parameterType="com.intellif.mozping.entity.People">
		insert into tb_people(id,name,age,address,edu)values(#{id},#{name},#{age},#{address},#{edu})
	</insert>


    <select id="findByNameAndAddress1" resultType="com.intellif.mozping.entity.People" parameterType="map">
        select * from tb_people p where p.name = #{name} and p.address = #{address}
    </select>

    <select id="findByNameAndAddress2" resultType="com.intellif.mozping.entity.People">
       select * from tb_people p where p.name = #{name} and p.address = #{address}
    </select>


    <select id="findByNameAndAddress3" resultType="com.intellif.mozping.entity.People"
            parameterType="com.intellif.mozping.querybean.PeopleQueryBean">
       select * from tb_people p where p.name = #{name} and p.address = #{address}
    </select>
    
</mapper>
  • Java代码

//JavaQueryBean
@Data
public class PlayerQueryBean {

    String team;
    Float height;
}

//实体类:
@Data
public class People {

    private int id;
    private String name;
    private int age;
    private String address;
    private String edu;//学士(Bachelor) 硕士(master)  博士(Doctor)

}


//Java接口:
public interface PeopleMapper {

    int addPeople(People people);

    List<People> findByNameAndAddress1(Map<String, Object> param);


    List<People> findByNameAndAddress2(PeopleQueryBean peopleQueryBean);

    List<People> findByNameAndAddress3(@Param("name") String name, @Param("address") String address);

}
  • 数据库记录
id name age address edu
1 Duncan 12 Beijing Doctor
2 Parker 20 tianjing Bachelor
3 Duncan 21 tianjing Bachelor
  • 测试代码:
@Test
    public void query() {
        SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactoryInstaceByConfig(CONFIG_FILE_PATH).openSession();
        PeopleMapper peopleMapper   = sqlSession.getMapper(PeopleMapper.class);
        HashMap map = new HashMap();
        map.put("name","Duncan");
        map.put("address","beijing");
        //方式1,map传参
        List<People> peoples1 = peopleMapper.findByNameAndAddress1(map);
        for (People p : peoples1) {
            System.out.println(p);
        }

        PeopleQueryBean peopleQueryBean = new PeopleQueryBean();
        peopleQueryBean.setName("Duncan");
        peopleQueryBean.setAddress("beijing");
        //方式2,javaBean传参
        List<People> peoples2 = peopleMapper.findByNameAndAddress2(peopleQueryBean);
        for (People p : peoples2) {
            System.out.println(p);
        }

        //方式3,注解传参
        List<People> peoples3 = peopleMapper.findByNameAndAddress3("Duncan","beijing");
        for (People p : peoples3) {
            System.out.println(p);
        }
    }
    
打印:
19:50:20.653 [main] DEBUG c.i.m.d.P.findByNameAndAddress1 - ==> Parameters: Duncan(String), beijing(String)
19:50:20.668 [main] DEBUG c.i.m.d.P.findByNameAndAddress1 - <==      Total: 1
People(id=1, name=Duncan, age=12, address=beijing, edu=Doctor)
19:50:20.669 [main] DEBUG c.i.m.d.P.findByNameAndAddress2 - ==>  Preparing: select * from tb_people p where p.name = ? and p.address = ? 
19:50:20.669 [main] DEBUG c.i.m.d.P.findByNameAndAddress2 - ==> Parameters: Duncan(String), beijing(String)
19:50:20.671 [main] DEBUG c.i.m.d.P.findByNameAndAddress2 - <==      Total: 1
People(id=1, name=Duncan, age=12, address=beijing, edu=Doctor)
19:50:20.672 [main] DEBUG c.i.m.d.P.findByNameAndAddress3 - ==>  Preparing: select * from tb_people p where p.name = ? and p.address = ? 
19:50:20.672 [main] DEBUG c.i.m.d.P.findByNameAndAddress3 - ==> Parameters: Duncan(String), tianjing(String)
19:50:20.674 [main] DEBUG c.i.m.d.P.findByNameAndAddress3 - <==      Total: 1
People(id=3, name=Duncan, age=21, address=tianjing, edu=Bachelor)

二、出参

2.1 ResultType

  • 对于简单数据类型,例如查询总记录数、查询某一个用户名这一类返回值是一个基本数据类型的,直接写Java中的基本数据类型即可。
    <select id="countAll" resultType="int" >
       SELECT count(1) FROM tb_people
    </select>
  • 如果返回的是一个对象或者集合,并且SQL中的字段名称和对象中的属性是一一对应的,那么resultType也可以直接写一个对象(当然也可以写别名,这里也可以关闭自动映射开启下划线转驼峰),示例可以参照之前演示多参数传递的写法。

2.2 自动映射和失效

  • 使用自动映射的前提是:
    使用resultType
    Sql列名和JavaBean属性完全一致
  • 在使用resultType的时候,会做自动映射,自动映射默认是开启的,如果sql的命名和java字段一样,那就没有任何问题,前面的例子都是这样的情况。
  • 如果二者不一样,则转换会失败,查询到的就是null,所以这样的方式看起来简单但是约束比较重,必须要保证两边的字段一样,实际上不推荐使用,在阿里巴巴的
    java开发规范中都禁止使用,因此有了下面2种较为灵活的解决方法。

2.2.1 别名

  • 查询时,可以给查询结果取别名。在sql中给数据库的字段去一个别名,保证别名和javaBean中字段一样,因此即使数据库字段修改了,javaBean属性名也不需要修改,
    只要在映射文件中维护即可。

2.2.2 转换

  • 如果javaBean是按照驼峰命名规范,数据库是按照下划线的规范命名,可以关闭自动映射并开启下划线转驼峰,其实这样的方式也是一种自动映射,只是映射规则稍微修改
    了一点点,和之前的字动映射有一样的缺点,维护的时候2边要保持一致。按照良好的java编程规范,最好定义resultMap,这样即使java字段变化,也可以和数据库字段变化
    解耦,便于维护和扩展。

2.2.3 ResultMap

  • 查询的结果集如果是比较复杂的结果集,比如多表的关联,或者javaBean和sql中字段名不一样,那么可以自定义resultMap,其实是自定义一个转换规则,而且可以做复用,
    在很多查询中都可以使用,这也是最为推荐的方法,如下:
    映射文件:
    <select id="findAll" resultMap="BaseResultMap" >
       SELECT * FROM tb_people
    </select>

    <resultMap id="BaseResultMap" type="com.intellif.mozping.entity.People">
        <id column="id" property="id" />
        <result column="nameDb" property="name" />
        <result column="age" property="age" />
        <result column="address" property="address" />
        <result column="edu" property="edu" />
    </resultMap> 
	
  • 如上所示,我临时将数据库的name字段修改为nameDb字段,只需要在BaseResultMap修改即可,不需要修改java对象的代码,测试代码如下:
	@Test
    public void queryGetWithResultMap() {
        SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactoryInstaceByConfig(CONFIG_FILE_PATH).openSession();
        PeopleMapper peopleMapper = sqlSession.getMapper(PeopleMapper.class);

        List<People> peoples = peopleMapper.findAll( );
        for (People p : peoples) {
            System.out.println(p);
        }
    }

三、主键回写

  • 般情况下,主键有两种生成方式:主键自增长或者自定义主键(一般可以使用UUID),如果是自增长,Java可能需要知道数据添加成功后的主键。 在MyBatis中,可以通过主键回填来解决这个问题(推荐)。如果是第二种,主键一般是在Java代码中生成,然后传入数据库执行。

3.1 useGeneratedKeys

  • 将数据库生成的主键写回到javabean对应的属性中
    <!--传进来的对象不包含主键,数据库生成主键之后,将主键返回给java代码-->
    <insert id="addPeopleWithOutPrimaryKey" parameterType="com.intellif.mozping.entity.People" useGeneratedKeys="true" keyProperty="id">
		insert into tb_people(name,age,address,edu)values( #{name},#{age},#{address},#{edu})
	</insert>
	
	测试代码:
	public class Test06 {

    private static final String CONFIG_FILE_PATH = "mybatis/mybatis-config-05.xml";

    @Test
    public void add() {
        SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactoryInstaceByConfig(CONFIG_FILE_PATH).openSession();

        People people = new People();
        people.setAge(54);
        people.setName("Ma yun");
        people.setAddress("hangzhou");
        people.setEdu("unKnow");

        PeopleMapper peopleMapper = sqlSession.getMapper(PeopleMapper.class);
        int rowAffected = peopleMapper.addPeopleWithOutPrimaryKey(people);
        System.out.println("The rows be affected :" + rowAffected);
        System.out.println("The primary key is:" + people.getId());
        //显示提交事务
        sqlSession.commit();
        sqlSession.close();
    }
}

打印:
The rows be affected :1
The primary key is:8

3.2 selectKey

  • 主键回写也可以使用的写法,经测试和上面的方法效果是一样的,配置如下:
    <insert id="addPeopleWithOutPrimaryKey1" parameterType="com.intellif.mozping.entity.People">
        <selectKey keyProperty="id" resultType="int">
            select LAST_INSERT_ID()
        </selectKey>
	    insert into tb_people(name,age,address,edu)values( #{name},#{age},#{address},#{edu})
	</insert>

四、小结

  • 本文主要从参数的角度分析了映射文件部分的内容,分为入参和出参2个方面
  • 入参方面包括单个参数和多参数,单个参数需要防止sql注入,多参数需要考虑可读性
  • 出参方面需要考虑可维护性和复用性
  • 另外还给出了主键回写的方法

猜你喜欢

转载自blog.csdn.net/my_momo_csdn/article/details/91045404