zrz8an5opci14y / mybatis1807 / Mybatis_Day02

分享

   

Mybatis_Day02

2019-09-24  zrz8an5op...

回顾】作业

  1. 完成根据用户名删除数据的功能;
  2. 完成将所有用户的密码设置为某个值的功能;
  3. 完成删除用户表中所有数据的功能。

在MyBatis中,默认不允许添加2个参数,否则会报错,解决方案待续。

1. 单元测试

在单元测试中,每个测试方法都需要执行相同的前置代码和后置代码,则可以自定义2个方法,分别在这2个方法中执行前置代码和后置代码,并为这2个方法添加@Before@After注解,然后,在每个测试方法中,就不必再编写这些代码,最终,在执行测试方法之前,会自动调用添加了@Before注解的方法,在执行测试方法之后,会自动调用添加了@After注解的方法:

private AbstractApplicationContext ac;
private UserMapper userMapper;

@Before
public void doBefore() {
    // 加载Spring配置文件,获取Spring容器
    ac = new ClassPathXmlApplicationContext(
            "spring.xml");

    // 从Spring容器中获取对象
    // bean-id与接口名一致,首字母为小写
    userMapper
        = ac.getBean("userMapper", UserMapper.class);
}

@After
public void doAfter() {
    // 释放资源
    ac.close();
}

@Test
public void deleteUserById() {
    // 测试功能
    Integer id = 3;
    Integer rows = userMapper.deleteUserById(id);
    System.out.println("rows=" + rows);
}

2. 在MyBatis中查询数据

使用MyBatis执行查询操作时,抽象方法的设计中,返回值应该根据查询需求来决定,例如查询用户列表时,可以使用List<User>作为返回值类型,查询某个用户时,可以使用User作为返回值类型,查询的是计数等操作时,可以使用Integer作为返回值类型。

其实,无论执行什么样的查询,MyBatis的查询结果都是List集合,只不过,如果抽象方法声明的不是集合,而是具体的某个类型,例如User时,MyBatis会尝试从集合中获取第1个元素作为返回值!

查询是可能失败,即没有匹配的数据,在这种情况下,如果返回值是List集合,则返回的是空集合(集合对象是存在的,但是集合中没有任何元素),如果返回值是某个类型,则返回null。

在配置的XML映射中,每个查询对应的都是<select>节点,该节点必须配置resultTyperesultMap属性,用于表示查询结果的类型(即使返回值类型是Integer也必须配置)!

如果返回结果是User对象,则resultType的值是User类的全名,如果返回结果是List,其resultType也是User类的全名,而不是List的全名!

关于resultMap后续再介绍。

目标:查询所有用户的数据

在接口中添加抽象方法:

List<User> findAll();

配置以上方法的映射:

<select id="findAll" 
    resultType="cn.tedu.mybatis.entity.User">
    SELECT
        id, username, password, age, phone, email
    FROM t_user
</select>

目标:查询指定id的用户的数据

在接口中添加抽象方法:

User findUserById(Integer id);

配置以上方法的映射:

<select id="findUserById" 
    resultType="cn.tedu.mybatis.entity.User">
    SELECT
        id, username, password, age, phone, email
    FROM t_user 
    WHERE id=#{id}
</select>

练习:根据用户名查询用户数据。

目标:获取当前用户表中用户的数量

在接口中添加抽象方法:

Integer getCount();

配置以上方法的映射:

<select id="getCount"
    resultType="java.lang.Integer">
    SELECT COUNT(id) FROM t_user
</select> 

2. 涉及多参数的数据访问操作

在许多操作中,也许1个参数并不能满足需求,可能需要2个或更多个参数,例如修改某个用户的密码,设计的抽象方法可能是:

Integer changePassword(Integer id, String password);

然后,配置的映射应该是:

<update id="changePassword">
    UPDATE t_user
    SET password=#{password}
    WHERE id=#{id}
</update>

在MyBatis中,其实只支持1个参数,并且,Java源文件编译为class字节码文件后,也会丢失参数名称,所以,需要为每一个参数添加@Param注解:

Integer changePassword(
    @Param("id") Integer arg0, 
    @Param("password") String arg1);

则MyBatis会把这些参数全部封装在一个Map中,注解中的名称就是每个参数的Key,后续,在配置映射时,在#{}中填入的也就是注解的名称,即各参数在Map中的Key:

在设计MyBatis中的抽象方法时,如果参数超过1个,则每个参数之前都必须添加@Param注解,在映射的SQL语句中,#{}中填入也是注解中使用的名称!

3. 插入数据时获取数据自增长的id

在配置映射时,为<insert>节点添加2个属性:useGeneratedKeys="true"表示需要获取自动生成的键,通常就是数据表中的主键字段,即id字段,然后配置keyProperty="id",表示获取到的键的值(即自增长的id值)将要封装到哪个属性中(即实体类中的属性名):

经过以上配置后,当成功插入数据时,就会获取到该数据的自增长的id值,并且,会将值封装到参数对象中,即:调用Integer insert(User user)方法时,假设使用user1作为调用时的参数,当方法执行结果后,参数user1中就已经包含了id值!

@Test
public void insert() {
    // 测试功能
    User user = new User();
    user.setUsername("java");
    user.setPassword("1234");

    System.out.println("执行前:" + user);
    Integer rows = userMapper.insert(user);
    System.out.println("rows=" + rows);
    System.out.println("执行后:" + user);
}

执行结果例如:

4. 关联表查询操作

问题描述

存在班级学生数据,要求显示某个班级信息时,同时显示出该班级的所有学生。

准备工作

创建班级(1)表:

CREATE TABLE t_class (
    id INT AUTO_INCREMENT,
    name VARCHAR(20),
    PRIMARY KEY(id)
) DEFAULT CHARSET=UTF8;

创建学生(N)表:

CREATE TABLE t_student (
    id INT AUTO_INCREMENT,
    name VARCHAR(20),
    class_id INT,
    PRIMARY KEY(id)
) DEFAULT CHARSET=UTF8;

当数据表之间存在1对多关系时,在“多”的表中需要存储“1”的表的唯一标识,即存储“1”的表的id。

为了保证后续的数据查询,还应该添加一部分的测试数据:

INSERT INTO t_class (name) VALUES 
("JSD1806"), ("JSD1807"), ("JSD1808");

INSERT INTO t_student (name, class_id) VALUES
("Jack", 1),    ("Rose", 1),
("LiLei", 2),   ("HanMM", 2),
("Lucy", 3),    ("LiLi", 3),
("Bob", 1), ("LiMing", 1),
("Kitty", 2),   ("Tom", 1);

基于以上数据表,如果要获取班级信息的同时,还获取该班级的所有学生的列表,则执行的SQL查询应该是:

SELECT
    c.id AS class_id,
    c.name AS class_name,
    s.id AS student_id,
    s.name AS student_name
FROM t_class AS c
INNER JOIN t_student AS s
ON c.id=s.class_id
WHERE c.name="JSD1806";

基于每张数据表都应该有1个与之对应的实体类的原则,则在项目中应该有:

public class Clazz {
    private Integer id;
    private String name;
}

public class Student {
    private Integer id;
    private String name;
    private Integer classId;
}

即使有了以上2个类,却都无法满足查询结果的需求,即尝试在接口中声明抽象方法时:

??? getClassInfo(String className);

无法确定返回值类型!

针对这种情况,通常会在项目中创建VO类,即Value Object类!例如:

public class ClazzVO {
    private Integer classId;
    private Integer className;
    private List<Student> students;
}

通常,实体类与数据表是对应的,而VO类是与实际使用需求对应的!

当设计好了VO类,则查询的抽象方法可以是:

ClazzVO getClassInfo(String className);

可以发现,即使使用了VO类,查询的字段与ClazzVO类中的属性名称等都无法直接对应,则,在映射文件中,需要使用到<resultMap>

最后的执行结果为:

ClazzVO [
    classId=1, 
    className=JSD1806, 
    students=[
        Student [id=1, name=Jack, classId=null], 
        Student [id=2, name=Rose, classId=null], 
        Student [id=7, name=LiMing, classId=null], 
        Student [id=8, name=Bob, classId=null], 
        Student [id=10, name=Tom, classId=null]
    ]
]

小结

如果某次查询涉及多张表,存在关联查询,通常是没有匹配的实体类可以直接使用的,在这种情况下,就需要自定义VO类。

VO类与实体类的代码表现基本相似,只是定位不同,实体类是与数据表对应的,而VO类是为了满足编码需求,更方便的获取查询结果而存在的!

VO类的属性的设计原则完全取决于所需要执行的查询的结果。

产生了关联查询后,可以直接用VO类作为resultType,但是,如果查询结果中存在数据之间的1对多等关系,则需要配置<resultMap>

5. 动态SQL

在MyBatis中配置映射时,允许使用例如<if>此类的标签,使得每次执行的SQL语句可以产生动态调整,则称之为动态SQL。

目标:实现根据id修改用户信息,可修改的字段有:密码、年龄、手机号码、电子邮件,如果执行的参数中,某项数据为null,则不修改原有值,例如修改时,参数中没有年龄值,则不修改原有的年龄,其它字段也是相同的处理方式。

首先,在接口中声明抽象方法:

Integer changeInfo(
    @Param("id") Integer id,
    @Param("password") String password,
    @Param("age") Integer age,
    @Param("phone") String phone,
    @Param("email") String email);

然后,配置以上方法的映射:

UPDATE t_user
SET
    password=#{password},
    age=#{age},
    phone=#{phone},
    email=#{email}
WHERE id=#{id}

以上配置的执行效果会是:如果没有提供某个值,将会把对应的字段的值设置为null,而并非不修改原有值。

此类问题可以通过动态SQL的<if>标签来解决:

UPDATE t_user
SET
    password=#{password},

    <if test="age != null">
    age=#{age},
    </if>

    phone=#{phone},

    <if test="email != null">
    email=#{email}
    </if>
WHERE id=#{id}

执行以上代码时,如果提供了有效的age值(非null),则SQL语句为:

UPDATE t_user SET password=?, age=?, phone=?, email=? WHERE id=?

但是,如果没有提供age值,则SQL语句为:

UPDATE t_user SET password=?, phone=?, email=? WHERE id=?

注意:在编写动态SQL时,参数直接写名字即可,例如test="age != null"中的age就是参数的名称,不需要使用#{}这类的语法!

目标:一次删除多条数据,这些数据的id是作为参数体现的,但是,是没有规律的。

在接口中声明抽象方法:

Integer deleteUserByIds(List<Integer> ids);

以上方法的设计,参数可以是List<Integer>,也可以是Integer[]

然后,配置映射:

<!-- collection:使用哪个集合,取值使用list或array -->
<!-- item:每次遍历到的元素的名称 -->
<!-- separator:IN内部的各个值之间使用的分隔符 -->
<!-- open:遍历后的结果的起始字符 -->
<!-- close:遍历后的结果的结束字符 -->
<delete id="deleteUserByIds">
    DELETE FROM t_user 
    WHERE id IN 
    <foreach collection="list"
        item="id" separator=","
        open="(" close=")">
        #{id}
    </foreach>
</delete>

注意:在中的collection属性,当抽象方法只有1个参数时,取值为list或array,根据参数类型决定,当抽象方法的参数超过1个时,该属性的值为参数的名称(参数的注解中使用的名称)!

关于动态SQL,主要掌握<if><foreach>的使用!

【附】关于配置MyBatis映射没有代码提示的解决方案**

先连接达内公司内网,下载:http://schema.tedu.cn/proxy/dtd/ibatis-3-mapper.dtd

下载的文件存储到任意位置均可。

在Eclipse中打开设置,左侧选择XML > XML Catelog,并在右侧点击Add按钮:

然后,在左侧选择第1项,右侧选择到刚才下载的文件,然后在Key这一栏输入-//ibatis.apache.org//DTD Mapper 3.0//EN(在映射文件顶部Public字样右侧的字符串):

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多
    喜欢该文的人也喜欢 更多

    ×
    ×

    ¥.00

    微信或支付宝扫码支付:

    开通即同意《个图VIP服务协议》

    全部>>