mybatis基础学习(二)

299 阅读10分钟

在前面内容里,已经通过mybatis简单实现了一个查询功能,接下来主要通过XNL实现的方式,使用接口调用的方式,分别实现简单的CRUD的功能

mybatis XML方式

###1、select示例


以一个简单的用户权限控制为例,上面是权限控制对应表结构的ER图。在src/main/java下的tk.mybatis.simple.model包里新建对应的实体类。此处省略实体类的创建内容,五个类为SysUser,SysRole,sysPrivilege,SysUserRole,SysRolePrivilege

在src/mian/resource下的tk.mybatis.simple.mapper包内新建 mapper对应的xml文件:UserMapper.xmL、RoleMapper.xml、PrivilegeMapper.xml 、UserRoleMapper.xml和
RolePrivilegeMapper.xml 同时在src/main/java下新建tk.mybatis.simple.mapper包,在包中新建XML文件对应的接口类:UserMapper.java、RoleMapper.java、PrivilegeMapper.java、UserRoleMapper.java、RolePrivilege.java

接口类举例:

public interface UserMapper
{
}

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="tk.mybatis.simple.mapper.UserMapper">
</mapper>

在使用接口进行方法调用的时候,mapper.xml中的namespace必须配置为接口类的全限定名称。
准备完对应的接口和XML文件后,在mybatis-config.xml配置文件的mappers元素中配置所有的mapper:

<mappers>
    <!--也支持扫包的形式-->
    <!--<package name="tk.mybatis.simple.mapper"/>-->
    <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml"/>
    <mapper resource="tk/mybatis/simple/mapper/UserMapper.xml"/>
    <mapper resource="tk/mybatis/simple/mapper/RolesMapper.xml"/>
    <mapper resource="tk/mybatis/simple/mapper/PrivilegeMapper.xml"/>
    <mapper resource="tk/mybatis/simple/mapper/UserRoleMapper.xml"/>
    <mapper resource="tk/mybatis/simple/mapper/RolePrivilegeMapper.xml"/>
</mappers>

这里支持扫包的模式,扫包的时候会对配置的包下面的所以接口进行循环操作:
1、判读接口对应的命名空间是否存在,存在则抛出异常,不存在则继续操作
2、加载接口对应的xml映射文件,通过接口的全路径名称加xml来查找并解析,这就是为什么xml文件名和接口名必须一致的原因
3、处理接口中的方法

在基本配置完成后,若要实现一个简单的查询功能,只要在xml文件中添加<select>标签,写上sql语句,做一些简单配置把查询结果映射到对象中即可。
这里做一个根据ID查询用户的示例,先在UserMapper接口中添加一个selectById方法,代码如下:

public interface UserMapper
{
    SysUser selectById(Long id);
}

然后在对应的UserMapper.xml中添加如下的<resultMap><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">
<mapper namespace="tk.mybatis.simple.mapper.UserMapper">
<resultMap id="userMap" type="tk.mybatis.simple.model.SysUser">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="userPassword" column="user_password"/>
    <result property="userEmail" column="user_email"/>
    <result property="userInfo" column="user_info"/>
    <result property="headImg" column="head_img" jdbcType="BLOB"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>

</resultMap>
<select id="selectById" resultMap="userMap">
    select * from sys_user where id = #{id}
</select>
</mapper>

这里主要关注两点:
1、接口中的方法和XML通过<select>标签的id属性进行关联,id的属性值和接口中的方法名是一致的,若方法名和id不一致就会报错。
2、<select>标签里的resultMap属性对应上面的<resultMap>,这里是为了方便映射,做了详细配置,也可以参照前一篇内容中使用resultType属性,则无需进行额外配置,两者区别在于是否有具体的映射关系,有映射关系则可以使用select * 这样的查询操作,否则必须进行具体字段的查询,而且数据库字段必须和类字段一致,若不一致可以使用as进行转换。具体的其他用法会在后面用到的地方具体描述。

```是一种和重要的配置结果映射的方法
具体的使用可以参照Mybatis---resultMap部分的具体解析

接口中定义的返回值类型必须和泊位中配置的resultType 类型一致,否则就会因为类型不一致而抛出异常。这里新增一个查询所有用户的方法,来区分不同返回值的情况,新增代码如下:

public interface UserMapper
{
    SysUser selectById(Long id);

    List<SysUser> selectAll();
}

#
select id, user_name userName, user_password userPassword, user_ema userEmail, user_info userInfo, head_img headImg, create_time createTime from sys_user
观察一下U serMapper.xmJ 中selectByid 和selectAll 的区别: selectByid 中使用了resultMap 来设置结果映射,而se lectAll 中则通过result Type 直接指定了返回结果的类型。可以发现,如果使用result Type 来设置返回结果的类型,需要在SQL 中为所有列名和属性名不一致的列设置别名,通过设置别名使最终的查询结果列和result Type 指定对象的属性名保持一致,进而实现自动映射。

在数据库中,由于大多数数据库设置不区分大小写,因此下画线方式的命名很常见,如user_name、user_email 。在Java中,一般都使用驼峰式命名,如userName、userEmail。因为数据库和Java中的这两种命名方式很常见,因此MyBatis还提供了一个全局属性apUnderscoreToCamelCase,通过配置这个属性为true 可以自动将以下画线方式命名的数据库列映射到Java对象的驼峰式命名属性中。这个属性默认为false,如果想要使用该功能,需要在MyBatis的配置文件增加如下配置:

<settings>
    <setting name= ” mapUnderscoreToCamelCase ” value=” true ” />
</settings>

使用该配置的时候,前面的selectAll方法就无需as起别名。

测试:
为方便测试,编写一个基础测试类,用于初始化数据库连接:

public class BaseMapperTest {

private static SqlSessionFactory sqlSessionFactory;

@BeforeClass
public static void init(){
    try {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        reader.close();
    }catch (Exception e){
        e.printStackTrace();
    }
}

public static SqlSession getSqlSessionFactory() {
    return sqlSessionFactory.openSession();
}
}

编写测试类XXXMapperTest继承基础测试类BaseMapperTest:

public class SysuserTest extends BaseMapperTest {

@Test
public void selectByIdTest(){
    SqlSession sqlSession = getSqlSessionFactory();
    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        SysUser user = userMapper.selectById(1L);
        System.out.println("ID:"+user.getId()+"===name:"+user.getUserName()+"===password:"+user.getUserPassword()+
                "===info:"+user.getUserInfo()+"===email:"+user.getUserEmail()+"===createtime:"+user.getCreateTime());

    }catch (Exception e){
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}

@Test
public void selectAllTest(){
    SqlSession sqlSession = getSqlSessionFactory();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<SysUser> users = userMapper.selectAll();
    for(SysUser user : users ){
        System.out.println("ID:"+user.getId()+"===name:"+user.getUserName()+"===password:"+user.getUserPassword()+
                "===info:"+user.getUserInfo()+"===email:"+user.getUserEmail()+"===createtime:"+user.getCreateTime());
    }
}
}

到这里,简单的select查询就结束了,复杂的查询主要区别在于sql已经和返回对象,在后面再详细描述。

###2、insert示例
在UserMapper中新增insert方法:

int insert(SysUser sysUser);

在UserMapper.xml中新增insert标签

<insert id="insert" >
    insert into sys_user(
        id,user_name,user_password,user_ema,
        user_info,head_img,create_time)
    values(
        #{id},#{userName},#{userPassword},#{userEmail},
        #{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType = TIMESTAMP}
    )
</insert>

看一下<insert>元素的具体属性:
1、id:命名空间中的唯一标识符,需要与接口中的方法名一致
2、parameterType:指定传入的参数类名或别名,可选。不建议配置
3、flushCache:默认为true,任何时候只要语句被执行,则清空一二级缓存
4、timeout:设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数
5、statementType:对于STATMENT、PREPARED、CALLABLE,mybatis会使用对应的Statement、PrepareStatement、CallableStatement,默认值为PREPARED.
6、useGenerateKeys:默认为false。如果设置为true,mybatis会使用JDBC默认的getGeneraedKeys方法来去除由数据库内部生产的主键。
7、keyProperty:mybatis通过getGeneratedKeys获取主键值后将要赋值的属性名。如果希望得到多个数据库自动生产的列,属性值也可以以逗号分割作为属性名称列表
8、keyColumn:仅对INSERT和UPDATE有效。通过生成的键值设置表中的列名,这个设置仅在某些数据库中是必须的,当主键不是表的第一列的时候需要设置。如果需要得到多个生产的列,也可以逗号分隔
9、databaseId:如果配置了databaseIdProvider,mybatis会加载所有不带databaseId的或匹配当前databaseId的语句。如果同时存在带databaseId和不带databaseId的语句,后者会被忽略。
为防止类型错误,对于特殊的数据类型,建议指定具体的jdbcType值。

测试:

@Test
public  void insertTest(){
    SysUser sysUser = new SysUser();
    sysUser.setId(0000000000101L);
    sysUser.setUserName("testuser01");
    sysUser.setUserPassword("test01");
    sysUser.setUserEmail("test01@test.com");
    sysUser.setUserInfo("he is test01 for test!");
    sysUser.setHeadImg(null);
    sysUser.setCreateTime(new Date());

    SqlSession sqlSession = getSqlSessionFactory();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    Integer n = userMapper.insert(sysUser);
    //默认的sqlsession是不会自动提交的
    sqlSession.commit();
    System.out.println(n);
}

一个简单的insert方法就实现了,注意在这里,方法的返回值是int类型的,这里的返回值是改变数据数据条数。
这里提供两个方法获取insert对象的主键:

#####a、jdbc方式回显
新增一个insert2方法方便测试

int insert2(SysUser sysUser);

在XML文件中配置insert2的sql如下:

<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
    insert into sys_user(
    user_name,user_password,user_ema,
    user_info,head_img,create_time)
    values(
    #{userName},#{userPassword},#{userEmail},
    #{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType = TIMESTAMP}
    )
</insert>

这里使用了useGeneratedKeys方法来获取自增的主键值,通过keyProperty属性把主键值付给id。
测试方法中insert2执行并commit后,获取sysuser对象的id发现回显了,带回了id的值

#####b、使用selectkey方法获取id
新增一个insert3方法方便测试

int insert3(SysUser sysUser);

在XML文件中配置insert3的sql如下:

<insert id="insert3">
    insert into sys_user(
    user_name,user_password,user_ema,
    user_info,head_img,create_time)
    values(
    #{userName},#{userPassword},#{userEmail},
    #{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType = TIMESTAMP}
    )
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        select 1260488
    </selectKey>
</insert>

这里在<insert>标签里面新增了一个<selectKey>的标签,keyColumn、keyProperty和上面insert2方法中的用法一样,resultType是指定返回值对象,order是根据数据库类型指定,mysql使用‘AFTER’,因为主键值是insert语句执行完之后才能获取到,oracle使用‘BEFOR’,因为oracle数据库需要先从序列获取值,然后将值作为 主键插入到数据库中。
oracle中使用如下:

<insert id="insert3">
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        select SEQ_ID.nextval from dual
    </selectKey>
    insert into sys_user(
        id,user_name,user_password,user_ema,
        user_info,head_img,create_time)
    values(
        #{id},#{userName},#{userPassword},#{userEmail},
        #{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType = TIMESTAMP}
    )
</insert>

这里把<selectKey>标签放到前面只是为了表示执行顺序,具体放在上面还是下面对代码没影响,决定执行顺序的是order字段的值。sql语句中明确写出了id是因为这里插入id的是根据查询出来的值再进行插入的,如果没有会报错。

###3、update示例
写一个根据主键更新数据的方法作为示例:
在UserMapper接口中新增方法:
int updateById(SysUser sysUser);
在UserMapper.xml中新增update标签:

<update id="updateById">
    update sys_user
    set user_name=#{userName},
        user_password=#{userPassword},
        user_ema=#{userEmail},
        user_info=#{userInfo},
        head_img=#{headImg,jdbcType=BLOB},
        create_time=#{createTime,jdbcType=TIMESTAMP}
    where id=#{id}
</update>

测试:

@Test
public void updateByIdTest(){
    SysUser sysUser = new SysUser();
    sysUser.setId(1001L);
    sysUser.setUserName("testuser05_update");
    sysUser.setUserPassword("test05_update");
    sysUser.setUserEmail("test05_update@test.com");
    sysUser.setUserInfo("he is test05_update for test!");
    sysUser.setHeadImg(null);
    sysUser.setCreateTime(new Date());

    SqlSession sqlSession = getSqlSessionFactory();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    Integer n = userMapper.updateById(sysUser);
    //默认的sqlsession是不会自动提交的
    sqlSession.commit();
    System.out.println(sysUser.getId());
    System.out.println(n);
}

###4、delete示例
写一个根据id删除数据的方法作为示例:
在UserMapper接口里新增一个删除方法:
int deleteById(Long id);
在UserMapper.xml中新增对应的delete标签:

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

测试:

@Test
public void deleteById(){
    SqlSession sqlSession = getSqlSessionFactory();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    Integer n = userMapper.deleteById(1005L);
    //默认的sqlsession是不会自动提交的
    sqlSession.commit();
    System.out.println(n);
}

当然这里如果传参传User对象也是可以的,xml文件中不需要修改,直接修改接口参数为User对象就行。

###5、多个参数作为方法参数时的处理
如果要执行的CRUD方法有多个参数的时候应该如何处理?一般有两种方法:
1、新建一个map对象:把多个参数通过key和value传进去,但是这种方式要自己新建map对象,一般不推荐。
2、使用@Param注解的方式:
例:根据用户ID和角色的enabled状态获取角色对象
List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("userId")Long userId,@Param("enabled")Integer enabled);
在xml中使用如下:

<select id="selectRolesByUseridAndRoleEnabled" resultType="tk.mybatis.simple.model.SysRole" >
    select
        r.id,
        r.role_name roleName,
        r.enabled,
        r.create_by createBy,
        r.create_time createTime
    from sys_user u
        inner join sys_user_role ur on u.id=ur.user_id
        inner join sys_role r on ur.role_id=r.id
    where u.id=#{userld) and r.enabled=#{enabled)
</select>

若要传入的不是基本类型而是javaBean对象也是可以的,对应的则使用.的方式来获取:
List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("user")SysUser sysUser,@Param("role")SysRole sysRole);
xml中则使用where u.id=#{user.id) and r.enabled=#{role.enabled)从javaBean对象中获取对应的值。