MyBatis XML方式的基本用法

==我们设定了一个简单的权限控制需求,采用 RBAC (Role-Based Access Control ,基于角色

的访问控制)方式,这个简单的权限管理将会贯穿整本书中的所有示例。本章将通过完成权限

管理的常见业务来学习 MyBatisXML 方式的基本用法。

==在这里简单描述 下权限管理的需求 个用户拥有若干角色, 个角色拥有若干权限,

权限就是对某个资源(模块〉的某种操作(增、删、改、查),这样就构成了“用户·角色-权

限”的授权模型。在这种模型中,用户与角色之间、角色与权限之间

1、创建数据库表和插入注册数据

#首先,要创建五个表 用户表、角色表、权限表、用户角色关系表和角色权限关系表。
#为了方便对表进行直接操作,此处没有创建表之间的外键关系。对于表之间的关系,会通过业务逻辑来进行限制create table sys_user(
id bigint not null auto_increment COMMENT '用户id',
user_name varchar(50) comment '用户名',
user_password varchar(50) comment '密码',
user_email VARCHAR(50) COMMENT '邮箱',
user_info text COMMENT '简介' ,
head_img blob COMMENT '头像',
create_time datetime COMMENT '创建时间',
primary key (id)
)
alter table sys_user comment "用户表"create table sys_role(
id BIGINT not null auto_increment  COMMENT '角色id',
role_name VARCHAR(50) COMMENT '角色名称',
enabled int COMMENT '有效标志',
create_by BIGINT COMMENT '创建人id',
create_time datetime COMMENT '创建时间',
primary key (id)
)
alter table sys_role comment '角色表'create table sys_privilege(
id bigint not null auto_increment comment '权限id',
privilege_name varchar(50) comment '权限名称',
privilege_url varchar(200) comment '权限url',
primary key (id)
)
alter table sys_privilege comment '权限表'create table sys_user_role(
user_id BIGINT COMMENT '用户id',
role_id BIGINT COMMENT '角色id'
)
alter table sys_user_role comment '用户角色关联表';create table sys_role_privilege(
role_id BIGINT COMMENT '角色id',
privilege_id  BIGINT COMMENT '权限id'
)
alter table sys_role_privilege comment '用户权限关联表'INSERT INTO sys_user VALUES('1','admin','123456','admin@mybatis.tk','管理员',null,'2016-04-01 17:00:58');
INSERT INTO sys_user VALUES('1001','test','123456','test@mybatis.tk','测试用户',null,'2016-04-01 17:01:52');INSERT INTO sys_role VALUES('2','普通用户','1','1','2016-04-01 17:02:14');
INSERT INTO sys_role VALUES('1','管理员','1','1','2016-04-01 17:02:14');INSERT INTO sys_user_role VALUES ('1','1');
INSERT INTO sys_user_role VALUES ('1', '2' );
INSERT INTO sys_user_role VALUES ('1001', '2');INSERT INTO sys_privilege VALUES ('1','用户管理','/user');
INSERT INTO sys_privilege VALUES ('2','角色管理','/roles');
INSERT INTO sys_privilege VALUES ('3','系统日志','/logs');
INSERT INTO sys_privilege VALUES ('4','人员维护','/persons');
INSERT INTO sys_privilege VALUES ('5','单位维护','/companies');INSERT INTO sys_role_privilege VALUES('1','1');
INSERT INTO sys_role_privilege VALUES('1','3');
INSERT INTO sys_role_privilege VALUES('1','2');
INSERT INTO sys_role_privilege VALUES('2','4');
INSERT INTO sys_role_privilege VALUES('2','5');

2、创建实体类

#My Batis 默认是遵循“下画线转驼峰”命名方式的,所以在创建实体类时 般都按照这种方式进行创建。由于上面比较类似,因此这里给出用户表和用户角色关联表所对应的体,另外的表大家按照相同的规则编写即可先看第一个,用户表对应的实体类 SysUser的代码如下package com.orange.model;
import java.util.Date;
public class SysUser {private Long id;private String userName;private String userPassword;private String userEmail;private String userInfo;private byte[] headImg;private Date createTime;//省略get,set...
}#对于SysUser 实体类,首先需要注意的就是命名方式,它的类名和字段名都采用了“下画线转驼峰”方式。具体采用什么样的命名方式并不重要,在后面使用这些对象的时候,可以通过resultMap对数据库的列和类的宇段配置映射关系,MyBais中,关于数据库字段和 Java 类型的对应关系,不需要刻意去记,但需要注意特殊的类型“byte []飞这个类型 般对应数据库中的 BLOB LONGVARBINARY进制流有关的字段类型,其他类型详细的对应关系可以查看本书附录的内容。##特别注意
由于 Java 中的基本类型会有默认值,例如当某个类中存在 private int age ;宇段时创建这个类时, age 会有默认值 当使用 age 属性时,它总会有值 因此在某些情况下便无法实现使 age 为null 并且在动态 SQL 的部分,如果使用 age != null 进行判断结果总会为 true ,因而会导致很多隐藏的问题所以,在实体类中不要使用基本类型 基本类型包括 byte int short lo float double char boolean##考上面两个实体类的代码,请大家依次完成 SysRole SysPrivilege SysRolePrivilege 个类的代码创建实体类的过程比较枯燥 后面可以通过 MyBatis 官方提供的工具 MyBatis Generator MyBatis 代码生成器,简称 MBG )根据数据库表的信息自动生成这些类,以减少工作量。有关这个工具的使用方法将会在第5章详细介绍。在完成上面5个实体类的创建之后,下面起来学习 MyBatisXML 方式的基本用法

3、使用XML方式

##My Batis 正强大之处在于它的映射语句,这也是它的魔力所在。由于它的映射语句异,映射器的 XML 文件就显得相对简单。如果将其与具有相同功能的 JDBC 代码进行对比, 刻就会发现,使用这种方法节省了将近 95% 的代码量。 MyBatis 就是针对 SQL 构建的,并且 比普通的方法做的更好MyBatis 3.0 相比 2.0 版本的 个最大变化,就是支持使用接口来调用方法。以前使用 SqlSession 通过命名空间调用 MyBatis 方法时,首先需要用到命名空间和方法
id 成的字符串来调用相应的方法 当参数多于 个的时候,需要将所有参数放到 Map对象 通过 Map 传递多个参数,使用起来很不方便,而且还无法避免很多重复的代码。使用接口调用方式就会方便很多, MyBatis 使用 Java 的动态代理可以直接通过接口来调的方法,不需要提供接口的实现类,更不需要在实现类中使用 SqlSess 工∞以通过命名空 间间接调用 另外,当有多个参数的时候,通过参数注解@ Par am 设置参数的名字省去了 手动构造 Map 参数的过程,尤其在 Spring 中使用的时候,可以配置为自动扫描所有的接口类 ,直接将接口注入需要用到的地方。这些内容会在后面的章节中讲到,现在不能理解有关系因为使用接口更方便,而且它已被广泛使用,因此本书中会主要使用接口调用的方式,同提供 SqlSession 方式调用的例子来帮助大家理解 MyBatis

在 src/main/rsources com.orange.mapper 目录下创建 表各自对应的 XML

在 src/main/javacom.orange.mapper 目录下创建 表各自对应的 mapper

mybatis-config.xml 配置文件中的mappers元素中配置所有的mapper ,部分配置代码如下

<mappers>
<mapper resource=”com/orange/mapper/CountryMapper.xml ” / >
<mapper resource=”com/orange/mapper/UserMapper.xml ” / >
<mapper resource=”com/orange/mapper/RoleMapper.xml” />
<mapper resource=”com/orange/mapper/PrivilegeMapper.xml ” />
<mapper resource=”com/orange/mapper/UserRoleMapper.xml ” />
<mapper resource=”com/orange/mapper/RolePrivilegeMapper.xml ” />
</mappers>#这种配置方式需要将所有映射文件一一列举出来
#在此处进行配置,操作起来比较麻烦 因为此处所有的 XML 映射文件都有对应的 Mapp 接口,所以还有 种更简单的配置方式,代码如下<mappers>
<package name= ”com.orange.mapper” />
</mappers>这种配置方式会先查找 com.orange.mapper 包下所有的接口,循环对接口进行如下操作。
I. 判断接口对应的命名间是否己经存在,如果存在就抛出异常,不存在就继续进行接下
来的操作。
2. 加载接口对应的却也映射文件 将接口全限定名转换为路径 例如 将接口com.orange.UserMapper 转换为 com/orange/mapper/UserMapper.xml,.xml 为后缀搜索XMl资源,如果找到就解析xML
3. 处理接口中的注解方法。
因为这里的接口和 XML 映射文件完全符合上面操作的第 点,因此直接配置包名就能自动扫描包下 的接口和 XML 映射文件,省去了很多麻烦 准备好这 切后就可 开始学习具体的用法了

4、select 用法

在权限系统中有几个常见的业务,我们需要查询出系统中的用户、角色 权限等数据使用纯粹 JDB 时,需要写查询语句,并且对结果集进行手动处理 将结果映 到对象的属性中。使用 Mybatis时,只需要在 XMl 中添加 select 元素,写 SQL 再做些简单的配置,就可以将查询的结果直接映射到对象中先写 个根据用户 id 查询用户信息的简单方法。在 UserMapper 接口中添加一个selectByid 方法,代码如下

package com.orange.mapper;
import com.orange.model.SysUser;
import java.util.List;
public interface SysUserMapper {SysUser selectById(Long id);List<SysUser> selectAll();
}

然后在对应

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="com.orange.mapper.SysUserMapper"><resultMap id="userMap" type="com.orange.model.SysUser"><id property="id" column="id" ></id><result property="userName" column="user_name"></result><result property="userPassword" column="user_password"></result><result property="userEmail" column="user_email"></result><result property="userInfo" column="user_info"></result><result property="headImg" column="head_img"></result><result property="createTime" column="create_time" jdbcType="TIMESTAMP"></result></resultMap><select id="selectById" resultMap="userMap">select  * from sys_user where id = #{id}</select><select id="selectAll" resultMap="userMap">select  * from sys_user</select>
</mapper>

前面创建接口和 XML 时提到过,接口和 XML 是通过将 name space 的值设置为接口的全限定名称来进行关联的,那么接口中方法和 XML 又是怎么关联的呢?可以发现, XML 中的 select 标签的 id 属性值和定义的接口方法名是样的MyBatis 就是通过这种方式将接口方法和 XML 定义的 SQL 语句关联到 起的,如果接口方法没有和XML 中的 id 属性值相对应,启动程序便会报错。映射 XML 和接口的命名需要符合如下规则当只使用 XML 而不使用接 口的时候 names pace 的值可以设置为任意不重复的名称标签的 id 属性值在任何 候都不能出现英文句号“.”,并且同 个命名 间下不能现重复的 id因为接口方法是可以重载的,所以接口中可以出现多个同名但参数不同的方法,但是xML id 的值不能重复,因而接口中的所有同名方法会对应着 XML 中的同 id的方法。最常见的用法就是,同名方法中其中 个方法增加 RowBound 类型的数用于实前面创建接口和 XML 时提到过,接口和 XML 是通过将 name space 的值设置为接口的全限定名称来进行关联的,那么接口中方法和 XML 又是怎么关联的呢?可以发现, XML 中的 select 标签的 id 属性值和定义的接口方法名是 样的ρMyBatis 就是通过这种方式将接口方法和 XML 定义的 SQL 语句关联到 起的,如果接口方法没有和XML 中的 id 属性值相对应,启动程序便会报错。映射 XML 和接口的命名需要符合如下规则当只使用 XML 而不使用接 口的时候 names pace 的值可以设置为任意不重复的名称
标签的 id 属性值在任何 候都不能出现英文句号“.”,并且同 个命名 间下不能现重复的 id 因为接口方法是可以重载的,所以接口中可以出现多个同名但参数不同的方法,但是xML id 的值不能重复,因而接口中的所有同名方法会对应着 XML 中的同 id方法。最常见的用法就是,同名方法中其中 个方法增加 RowBound 类型的数用于实现分页查询。现分页查询。

测试代码

package com.orange;import com.orange.mapper.SysUserMapper;
import com.orange.model.SysUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.BeforeClass;
import org.junit.Test;import java.io.IOException;
import java.io.Reader;
import java.util.List;public class CountryMapperTest {private static SqlSessionFactory sqlSessionFactory;/** 前置初始化方法* */@BeforeClasspublic static void init() throws IOException {Reader reader = Resources.getResourceAsReader("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);reader.close();}@Testpublic void testSelectAll(){SqlSession sqlSession = sqlSessionFactory.openSession();SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);List<SysUser> sysUserList = mapper.selectAll();for (SysUser object : sysUserList) {System.out.println(object.getUserName());}sqlSession.close();}@Testpublic void testSelectOne(){SqlSession sqlSession = sqlSessionFactory.openSession();SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);SysUser sysUser = mapper.selectById(1l);System.out.println(sysUser.getUserName());sqlSession.close();}
}

上面两个 SELECT 查询仅仅是简单的单表查询,这里列举的是两种常见的情况,在实际业务
中还需要多表关联查询,关联查询结果的类型也会有多种情况,下面来列举一些复杂的用法
种简单的情形:根据用户 id 获取用户拥有的所有角色,返回的结果为角色集合,结
果只有角色的信息 ,不包含额外的其他字段信息。这个方法会涉及 sys user sys role
sys_user_role个表,井且该方法写在任 个对应的 Mapper 接口中都可以 。将这个
方法写到 UserMapper 中,代码如下。

虽然这个多表关联的查询中涉及了 个表,但是返回的结果只有 sys role 表中的信息,
所以直接使用 SysRole 作为返回值类型即可。大家可以参照之前的测试示例编写代码对此方
法进行测试。

为了说明第二种情形,我们设置一个需求(仅为了说明用法) 以第 种情形为基础,假设
查询的结果不仅要包含 sys role 中的信息,还要包含当前用户的部分信息(不考虑嵌套的情
况),例如增加查询列 .u ser ame as userName 。这时 resultType 该如何设置呢?
先介绍两种简单的方法,第一种方法就是在SysRole 对象中直接添加 userName 属性,
这样仍然使用 SysRole 作为返回值,或者也可以创建 个如下所示的对象。

public class SysRoleExtend extends SysRole {
private String userName ;
public String getUserName() {
return userName;
public void setUserName(String userName) {
this . userName = userName ;
}
}

resultType 设置为扩展属性后的 SysRoleExtend 对象,通过这种方式来接收多余的值。这种方式比较适合在需要少量额外宇段时使用,但是如果需要其他表中大量列的值时,这种方式就不适用了,因为我们不能将一个类的属性都照搬到另 个类中

针对这种情况,在不考虑嵌套 XML 配置(第6章会详细讲解)的情况下,可以使用第二种方法,代码如下。

public class SysRole {
//其他原有字段. ..
/**
食用户信息
*/
private SysUser user;
}

直接在 SysRole 中增加 SysUser 对象 字段名为 user ,增加这个字段后,修改 XML中的 selectRolesByUserid 方法。

<select id=”selectRolesByUserid” result Type=”com.orange.model.SysRole”>
select
r.id,
r.role name roleName,
r . enabled,
r . create by createBy,
r.create time createTime ,
u.user name as ” user.userName ”,
u . user email as ” user.userEmail”
from sys_user u
inner join sys_user role ur on u.id = ur.user id
inner join sys_role r on ur.role = r.
where u.id = #{user!d}
</select>

注意看查询列增加的两行,如下所示。
u.user name as ” user . userName ” ,
u.user iail a s ” u ser userE iai l ”

这里在设置别名的时候,使用的是“user 属性名”, user SysRole 中刚刚增加的属性, userName userEmail SysUser 对象中的属性 通过这种方式可以直接将值赋给user 字段中的属性

5、insert 用法

和上 select 相比, insert 简单很多 只有让它返回主键值时,由于不同数据库的主键键生成不同,这种情况下,首先从最简单的工nsert 方法开始

UserMapper 添加如下

int insert(SysUser sysUser);

UserMapper.xml添加

<insert id= ”insert” >
insert into sys_user(
values (
# {id} , # {userName} , # {userPassword} , # {userEmail}, #{userinfo} , #{headimg, jdbcType=BLOB},
#{createTime,jdbcType= TIMESTAMP})

id :命名空间中的唯 标识符,可用来代表这条语句
parameterType :即将传入的语句参数的完全限定类名或别名 这个属性因为 MyBatis 可以推断出传入语句的具体参数,因此不建议配置该属性flushCache :默认值为 true ,任何时候只要语句被调用,都会清空 缓存和
缓存timeout :设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数stateme tType :对于 STATEMENT PREPARED ALLABLE, MyBatis 会分别使对应的 Statement PreparedStatement Callable tatement ,默认 值为PREPAREDuseGeneratedKeys :默认值为 false 如果设置为 true, MyBatis 使用 JDBgetGeeratedKeys 方法来取出由数据库内部生成的主键• keyProperty: MyBatis 通过 getGeneratedKeys 获取 键值后将要赋值的属性如果希望得到多个数据库自动生成的列,属性值也可以是以 号分隔的属性名称列
keyColumn :仅对时SERT UPDATE 有用 通过生成的键值设置 中的列名, 这个设置仅在某些数据库(如 PostgreSQL )中是必须的, 当主键列不是 中的第 列时要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表database Id :如果配置了 databaseidProvider (4.6 节有详细配 方法 MyBatis
会加载所有的不带 databaseid 的或匹配当前 databaseid 的语句 如果同时存在带databaseid 和不带 databaseid 的语句,后者会被忽略此处 insert >中的 SQL 就是 个简单的 INSERT 语句,将所有的列都列values 中通过#{property }方式从参数中取出属性的值为了防止类型错误,对于 些特殊的数据类型,建议指定具体的 db Type
headimg 指定 BLOB 类型, createT me 指定 TIME STAMP 类型
特别说明!
BLOB 对应的类型是 ByteArrayinputStream ,就是二进制数据流
由于数据库区分 date time datetime 类型,但是 Java 中一般都使用 va.ut l. Date
类型 因此为了保证数据类型的正确,需要手动指定日期类型, date tim date me
应的 JDBC 类型分别为 DATE TIME TIMESTAMP

测试

    @Testpublic void testSelect3(){SqlSession sqlSession = sqlSessionFactory.openSession();SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);SysUser sysUser = new SysUser();sysUser.setUserName("orange");sysUser.setUserPassword("666");int insert = mapper.insert(sysUser);//手动提交事务sqlSession.commit();System.out.println(insert);sqlSession.close();}

使用 JDBC 方式返回主键自增的值

在使用主键自增(如 MySQ SQL Serv 数据库)时,插入数据库后可能需要得到自增的

主键值,然后使用这个值进行一些其他的操作。现在,增加 insert2 方法,复制 insert

#接口添加
int insert2(SysUser sysUser);#xml添加
<insert i d=” insert2 ” useGeneratedKeys=” true ” keyProperty=” id” >
insert into sys_user(
user_ name, user_password, user_email ,
user info, head img , create time)
values(
#{userName}, #{userPassword}, #{userEmail} ,
#{u serinfo }, #{headimg , jdbcType=BLOB} ,
#{ createTime , jdbcType= TIMESTAMP})
</insert>

useGeneratedKeys=” true ”

keyProperty=” id”

useGeneratedKeys 设置为 true 后, MyBatis

会使用 JDBC getGeneratedKeys法来取出 由数据库内部生成的主键。获得主键值后将其赋值给 keyProperty 配置的 id 属性。当需要设置多个属性时,使用逗号隔开,这种情况下通常还需要设置 keyCo lumn 属性 按顺

序指定数据库的列,这里列的值会和 keyProperty 配置的属性一一对应。由于要使用数据库返回的主键值,所以 上下两部分的列中去掉了 id 列和对应的#{ id }属性。

使用 selectKey 返回主键的值

上面这种回写主键的方法只适用于支持主键自增的数据库。有些数据库(如 Oracle )不提供主键自增的功能,而是使用序列得到 个值,然后将这个值赋给 id ,再将数据插入数据库对于这种情况,可以采用另外 种方式:使用< sele ctKey>标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库

为了让大家能看到这种方法的效果,分别用 MySQL Oracle 数据库举两个例子 先来看

MySQL 的例子。

在接口和 XML 中再新增 insert3 方法, UserMapper 接口的代码如下
int insert3(SysUser sysUser) ;
MyBatis XML 75式的墓本用法 I 41
上面的代码和前两个接口方法仍然 样,不同的还是 UserMapper xml 中的代码, 具体 下。
<insert id nsert3
insert into sys user(
user name , user password, user email ,
user_ info , head_img , create_time)
values(
#{us erName} , #{userPassword} , #{userEmail} ,
#{u se rinfo} , #{headimg , jdbcType=BLOB} ,
#{createTime , jdbcType= TIMESTAMP} )
<selectKey keyColumn=” id” resultType=” long” keyProperty=” id” order=” AFTER” >
SELECT LAST INSERT ID ()
</selec tKey>
</ insert>

注意看下面这段代码,和 insert 相比增加了 selectKey 标签
<selectKey keyColumn=” id” resultType=” long” keyProperty=” id” order=” AFTER” >
SELECT LAST INSERT ID ()

selectKey 标签 ke yColumn keyProperty 和上面 useGeneratedKeys 的用法含
义相同 ,这 resu ltT ype 用于设置返回值类型 rder 属性的设置和使用的数据库有关
MySQL 数据库中, order 属性设置的值是 AFTER,因为当前记录的主键值在 insert 语句
执行成功后才能获取 而在 Oracle 数据库中, order 的值要设置为 BEFORE ,这是因为 Oracl
中需要先从序列获取值,然后将值作为主键插入到数据库中

注意!

Oracle 方式的副SERT 语句中明确写出了 id 和值{ id },因为执行 selectKey 中的

语句后 id 就有值了,我们需要把这个序列值作为主键值插入到数据库中,所以必须指定 id

列,如果不指定这一列,数据库就会因为主键不能为空而抛出异常

oracle

<insert id=” insert3” >
<selectKey keyColumn resultType long keyProperty order BEFORE
SELECT SEQ ID.nextval from dual
</selectKey>
insert into sys_user(
id, user ne user_password, user email ,
user info, head img , create time)
values (
#{id} , # {userName} , #{userPassword} , #{userEmail} ,
# {user Info) , # { headlmg, j dbcType=BLOB) , # { creat eTime , j db Type TH TAMP))
</insert>

以上是对 selectKey 标签中属性的介绍,接着看

selectKey 元素中的内容。它

内容就是 个独立的 SQL 语句,在 Oracle 示例中, SELECT

SEQ_ ID.nextval from d u al

个获取序列的 SQL 语句。 MySQL 中的 SQL 语句 SELECT LAST_ INSERT_ ID ()用于获

数据库中最后插入的数据的

ID

以下是其他 些支持主键自增的数据库配置 el ectKey

中回写主键的 SQL

DB2 使用 VALUES IDENTITY VAL LOCAL ()
• MYSQL 使用 SELECT LAST_INSERT_ID ()
• SQLSERVER 使用 SELECT SCOPE IDENTITY ()。
• CLOUDSCAPE 使用 VALUES IDENTITY VAL LOCAL ()
• DERBY 使用 VALUES IDENTITY VAL LOCAL ()
• HSQLDB 使用 CALL IDENTITY ()
SYBASE 使用 SELE CT @@IDENTITY
• DB2 MF 使用 SELECT IDENTITY VAL LOCAL(} FROM SYSIBM. SYSDUMMYl
• FORMIX 使用 select dbinfo ('sq lea. sqlerrdl ’ ) from systables where
tabid=l

测试

    @Testpublic void testSelect5(){SqlSession sqlSession = sqlSessionFactory.openSession();SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);SysUser sysUser = new SysUser();sysUser.setUserName("orange");sysUser.setUserPassword("666");int insert = mapper.insert3(sysUser);System.out.println(sysUser.getId());sqlSession.commit();System.out.println(insert);sqlSession.close();}

以上就是 insert 的基本用法,后面介绍动态 SQL 的时候还会涉及更多的 insert 法。
节继续学习和 insert 用法相似的 update 用法

6、update 用法

先来看 个简单的通过主键更新数据的 update 方法的例子 UserMapper 接口中以下方法

    #接口int updateById(SysUser sysUser);#xml<update id="updateById">update sys_userset user_name= #{userName} ,user_password= #{userPassword},user_email = #{userEmail} ,user_info= #{userinfo} ,head_img = #{headimg , jdbcType=BLOB} ,create_time = #{createTime , jdbcType=TIMESTAMP}where id = #{id}</update>

7、delete 用法

delete update 类似,下面也用 个简单的例子说明。在 UserMapper 中添加 个简单的例子,代码如下。

int deleteByid(Long id);
根据主键删除数据的时候,如果主键只有 个字段,那么就可以像这个方法 样使用id,这个方法对应 userMapper.xml 中的代码如下。
<delete id=”deleteByid” >
delete from sys_user where id = #{id}
</delete>
注意接口中 int deleteByid(Long id 方法的参数类型为 Long id ,如果将参数类型修改如下,也是正确的。

8、多个接口参数的用法

通过观察,不难发现目前所列举的接口中方法的参数只有 个,参数的类型可以分为两种:一种是基本类型,另一种是 JavaBean当参数是一个基本类型的时候,它在 XML 文件中对应的 SQL 语句只会使用一个参数,例delete 方法。当参数是一个 JavaBean 类型的时候,它在 XML 文件中对应的 SQL 语句会有多个参数,例如 insert update 方法在实际应用中经常会遇到使用多个参数的情况 前面几节的例子中,我们将多个参数合并JavaBean 中,并使用这个 JavaBean 作为接口方法的参数。这种方法用起来很方便,但并不适合全部的情况,因为不能只为了两三个参数去创建新的 Java bean 类,因此对于参数比较少的情况 ,还有两种方式可以采用:使用 Map 类型作为参数或使用@Param 注解。使用 Map 类型作为参数的方法,就是在 Map 中通过 key 来映射 XML SQL 使用的参数值名字, value 用来存放参数值,需要多个参数时,通过 Map key-value 方式传递参数值 ,由于这种方式还需要自己手动创建 Map 以及对参数进行赋值,其实并不简洁,所以对这种方式只做以上简单介绍,接下来着重讲解使用@ Param 注解的方式。先来看一下 ,如果在接口中使用多个参数但不使用@Param 注解会发生什么错误现在要
根据用户 id 和角色的enabled状态来查询用户 的所有角色,定义个接口方法,代码如下。

#SysRoleMapper
List<SysRole> selectRolesByUseridAndRoleEnabled(Long userid, Integer enabled);#xml<select id="selectRolesByUseridAndRoleEnabled" resultType="com.orange.model.SysRole">select r.id,  r.role_name roleName, r.enabled,r.create_by createBy, r.create_time createTimefrom sys_user uinner join sys_user_role ur on u.id = ur.user_idinner join sys_role r on ur.role_id = r.idwhere u.id = #{userld)  and r.enabled = #{enabled)</select>

现在,在接口方法的参数前添加@ Par am 注解,代码如下

List<SysRole> selectRolesByUseridAndRoleEnabled(@Param("userid") Long userid,@Param("enabled") Integer enabled);

参数配直@ Par am 注解后, MyBatis 就会自动将参数封装成 Map 类型,@ Par am 注解值
作为 Map 中的 key ,因此在 SQL 部分就可以通过配置的注解值来使用参数。
到这里大家可能会有 个疑问:当只有 个参数(基本类型或拥有 Type Handler 配置的
型)的时候,为什么可以不使用注解?这是因为在这种情况下(除集合和数组外) MyBatis
不关心这个参数叫什么名字就会直接把这个唯 的参数值拿来使用。
以上是参数类型比较简单时使用@ Par am 注解的例子 当参数类型是 JavaBean 的时候,
略有不同 将接口方法中的参数换成 JavaBean 类型,代码如下

List<SysRole> selectRolesByUserAndRole(
@Param (”user”) SysUser user,
@Param (”role ”) SysRole role);

这时,在 XML 中就不能直接使用#{ userid }和#{ enabled }了,而是要通过点取值方式
使用#{ user.id }和#{ role.enabled }从两个 JavaBean 中取出指定属性的值 修改好对应的XML 文件后,大家可以自行完善代码并进行测试。
除了以上常用的参数类型外,接口的参数还可能是集合或者数组 本章还不会涉及这方面
的用法,有关集合和数组的用法可以阅读 4.4 节的内容提前了解

9、Mapper 接口动态代理实现原理

要想理解本节内容,需要具备 JDK 动态代理基础
通过上面的学习,大家可能会有 个疑问,为什么 Mapper 接口没有实现类却能被正常调用呢?
这是因为 MyBaits Mapper 接口上使用了动态代理的一种非常规的用法,熟悉这种动态代理的用法不仅有利于理解 MyBatis 接口和 XML 关系,还能开阔思路 接下来提取出这种动态代理的主要思路,用代码来为大家说明。

假设有 个如下的 Mapper 接口。

publ interface UserMapper { List<SysUser> selectAll();
}

这里使用 Java 动态代理方式一个代理类,代码如下:

package com.orange.proxy;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
public class MyMapperProxy<T> implements InvocationHandler {private Class<T> mapperinterface;private SqlSession sqlSession;public MyMapperProxy(Class<T> mapperinterface, SqlSession sqlSession ) {this.mapperinterface = mapperinterface;this.sqlSession = sqlSession;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//接口方法中的参数也有很多情况 ,这里只考虑没有有参数的情况List<T> list= sqlSession.selectList(mapperinterface.getCanonicalName() + "."+ method .getName());//返回位也有很多情况,这里不做处理直接返回return list;}
}

测试代码如下。

    @Testpublic void testSelect7(){SqlSession sqlSession= sqlSessionFactory.openSession();MyMapperProxy userMapperProxy = new MyMapperProxy(SysUserMapper.class, sqlSession) ;SysUserMapper userMapper = (SysUserMapper) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{SysUserMapper.class},userMapperProxy);List<SysUser> user= userMapper.selectAll();}

从代理类中可以看到,当调用 个接口的方法时,会先通过接口的全限定名称和当前调用
的方法名的组合得到 个方法 id ,这个 id 的值就是映射 XML 中namespace 和具体方法 id
的组合。所以可以在代理方法中使用 sqlSession 以命名空间的方式调用方法。通过这种方式
可以将接口和 XML 文件中的方法关联起来。这种代理方式和常规代理的不同之处在于,这里
没有对某个具体类进行代理,而是通过代理转化成了对其他代码的调用。由于方法参数和返回值存在很多种情况,因此 MyBatis 的内部实现会比上面的逻辑复杂得多,正是因为 MyBatis 对接口动态代理的实现,我们在使用接口方式的时候才会如此容易。如果大家对 MyBatis 源码感兴趣,可以通过第 11 章的内容了解 MyBatis 的源码并深入学习。通过本节这个简单的例子,我们可以了解 MyBatis 动态代理实现的方式,同时也学会一种编程思路:可以通过动态代理这个桥梁将对接口方法的调用转换为对其他方法的调用

本章小结

在本章中,我们通过 个简单的需求学习了 MyBatis XML 方式的各种基本用法章会通过注解的方式来实现这一章 中列举的大部分方法,所以大家在学习下一章 的时候可以和本章的内容进行对比,通过对比学习更好地掌握这两种方式的用法。

补充知识:

java的动态代理

关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式–代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。

一、静态代理

1、静态代理

静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

2、静态代理简单实现

根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

/*** 创建Person接口* @author Gonjan*/
public interface Person {//上交班费void giveMoney();
}

Student类实现Person接口。Student可以具体实施上交班费的动作。

public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic void giveMoney() {System.out.println(name + "上交班费50元");}
}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

/*** 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为* @author Gonjan**/
public class StudentsProxy implements Person{//被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if(stu.getClass() == Student.class) {this.stu = (Student)stu;}}//代理上交班费,调用被代理学生的上交班费行为public void giveMoney() {System.out.println("张三最近学习有进步!");stu.giveMoney();}
}

测试

public class StaticProxyTest {public static void main(String[] args) {//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成Person zhangsan = new Student("张三");//生成代理对象,并将张三传给代理对象Person monitor = new StudentsProxy(zhangsan);//班长代理上交班费monitor.giveMoney();}
}

二、动态代理

1.动态代理

代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

2、动态代理简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

创建一个动态代理对象步骤,具体代码见后面:

 #1、创建一个InvocationHandler对象//创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);#2、使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClassClass<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});#3、获得stuProxyClass 中一个带InvocationHandler参数的构造器constructorConstructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);#4、通过构造器constructor来创建一个动态实例stuProxyPerson stuProxy = (Person) cons.newInstance(stuHandler);#就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化://创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。**
**首先是定义一个Person接口:

/*** 创建Person接口* @author Gonjan*/
public interface Person {//上交班费void giveMoney();
}

创建需要被代理的实际类:

public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic void giveMoney() {try {//假设数钱花了一秒时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name + "上交班费50元");}
}

再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

public class MonitorUtil {private static ThreadLocal<Long> tl = new ThreadLocal<>();public static void start() {tl.set(System.currentTimeMillis());}//结束时打印耗时public static void finish(String methodName) {long finishTime = System.currentTimeMillis();System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");}
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

public class StuInvocationHandler<T> implements InvocationHandler {//invocationHandler持有的被代理对象T target;public StuInvocationHandler(T target) {this.target = target;}/*** proxy:代表动态代理对象* method:代表正在执行的方法* args:代表调用目标方法时传入的实参*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理执行" +method.getName() + "方法");*/   //代理过程中插入监测方法,计算该方法耗时MonitorUtil.start();Object result = method.invoke(target, args);MonitorUtil.finish(method.getName());return result;}
}

做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:

public class ProxyTest {public static void main(String[] args) {//创建一个实例对象,这个对象是被代理的对象Person zhangsan = new Student("张三");//创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);//代理执行上交班费的方法stuProxy.giveMoney();}
}

我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。

上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。

四、动态代理原理分析

1、Java动态代理创建出来的动态代理类

上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤(红色标准部分):

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}

其实,我们最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs);这句,这里产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,我这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件时缓存在java虚拟机中的,我们可以通过下面的方法将其打印到文件里面,一睹真容:

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";try(FileOutputStream fos = new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println("代理类class文件写入成功");} catch (Exception e) {System.out.println("写文件错误");}

对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;public final class $Proxy0 extends Proxy implements Person
{private static Method m1;private static Method m2;private static Method m3;private static Method m0;/***注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白*为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个*被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。**super(paramInvocationHandler),是调用父类Proxy的构造方法。*父类持有:protected InvocationHandler h;*Proxy构造方法:*    protected Proxy(InvocationHandler h) {*         Objects.requireNonNull(h);*         this.h = h;*     }**/public $Proxy0(InvocationHandler paramInvocationHandler)throws {super(paramInvocationHandler);}//这个静态块本来是在最后的,我把它拿到前面来,方便描述static{try{//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}/*** *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。*this.h.invoke(this, m3, null);这里简单,明了。*来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,*再联系到InvacationHandler中的invoke方法。嗯,就是这样。*/public final void giveMoney()throws {try{this.h.invoke(this, m3, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。}

jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

五、总结

生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

后面补充,知其然,还要知其所以然。

mybatis从入门到精通(刘增辉著)-读书笔记第二章相关推荐

  1. mybatis从入门到精通(刘增辉著)-读书笔记第一章

    前言: 本读书笔记共11章节 本版本采用idea编写,不采用作者书中所说的eclipse jdk8 maven3.6.1 mysql5.7 1.idea新建maven项目,配置pom.xml < ...

  2. mybatis从入门到精通(刘增辉著)-读书笔记第五章

    Mybatis 代码生成器 在学习第 MyBatis 的基本用法时,我 写了很多单表的增.删.改.查方法,基本上每个表都要有这些方法 这些方法都很规范并且也比较类似当数据库表的字段 较少的时候,写起来 ...

  3. mybatis从入门到精通(刘增辉著)-读书笔记第四章

    MyBatis动态sql MyBatis 的强大特性之 便是它的动态 SQL .使用过 JDB 或其他类似框架的人都会知道,根据不同条件拼接 SQL 语句时不仅不能忘了必要的空格,还要注意省略掉列名列 ...

  4. mybatis从入门到精通(刘增辉著)-读书笔记第三章

    MyBatis 注解方式的基本用法 My ti 注解方式就是将 SQL 语句直接写在接口上.这种方式的优点是 对于需求比较简单的系统,效率较高.缺点是 SQL 有变化时都需要重新编译代码, 般情况下不 ...

  5. 《数据库设计入门经典》读书笔记——第二章:工作场所中的数据库建模

    感觉这章讲的技术点很少,我就不想写了,这章就跳过了. 转载于:https://www.cnblogs.com/tuhooo/p/8468760.html

  6. MyBatis从入门到精通(1):MyBatis入门

    作为一个自学Java的自动化专业211大学本科生,在学习和实践过程中"趟了不少雷",所以有志于建立一个适合同样有热情学习Java技术的参考"排雷手册". 最近在 ...

  7. MyBatis从入门到精通(一):MyBatis入门

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. MyBatis简介 ​ 2001 ...

  8. MyBatis从入门到精通 PDF 完整版

    给大家带来的一篇关于MyBatis相关的电子书资源,介绍了关于MyBatis.入门到精通方面的内容,本书是由电子工业出版社出版,格式为PDF,资源大小116.8 MB,刘增辉编写,目前豆瓣.亚马逊.当 ...

  9. MyBatis从入门到精通(三):MyBatis XML方式的基本用法之多表查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. 多表查询 上篇博客中,我们示例的 ...

最新文章

  1. 三维场景图:用于统一语义、三维空间和相机的结构
  2. python 报错 UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xd3 in position 解决方法
  3. go语言笔记——是c开发的 lex yacc进行词法和语法分析,go不支持函数和运算符重载,不支持类型继承,也不支持断言,还有泛型...
  4. httpd反代 + tomcat cluster + redis会话保持
  5. python数据输出_python数据输出
  6. c++11仔细地将参数传递给线程std::thread
  7. python实战项目前后端分离flask_Flask Vue 构建前后端分离的应用
  8. Part5 数据的共享与保护 5.4类的友元5.5共享数据的保护
  9. 张泉灵:时代抛弃你时,连一声再见都不会说
  10. 魅族2016Java互联网方向其中一道笔试题--青蛙跳台阶问题
  11. 3) Maven 目录结构
  12. mysql计算增长率
  13. 斗罗大陆斗神再临服务器维修,斗罗大陆斗神再临攻略汇总:FAQ常见问题解答[多图]...
  14. 如何处理Git中没有小绿勾的问题
  15. 高手速成android开源项目【View篇】(转)
  16. 窥探MySQL索引与事务
  17. S7-1212C AC/DC/DLY作为PN主站通过PROFINET转Modbus RTU网关设备与Micro Logix 140
  18. 学习笔记之抽取和内插
  19. idea快速创建接口实现类快捷键
  20. 2021 最新 Nginx 常用配置清单

热门文章

  1. VC开发环境 路径宏
  2. L1-046 整除光棍 (20 分)模拟除法
  3. Error in DESeqDataSet(se, design = design, ignoreRank) : some values in assay are not integers
  4. 天融信EDR 彻底卸载方法
  5. Manjaro Linux 17.0.2 KDE环境安装、配置记录
  6. 大数据核心技术与应用实战峰会(上):大数据在多行业内大放异彩
  7. 要你命三千又三千的成长之旅
  8. C语言一战式学习网址链接
  9. 痴情人肠断 无情最逍遥
  10. 树莓派挂载windows共享文件夹