在 MyBatis框架快速入门(一),搭建MyBatis开发环境 中已经把开发环境搭起来了,并测试了一个简单的查询操作,接下来就是对之前配置文件的繁琐重复操作进行简化,在此基础上通过使用映射器接口代理对象的方式实现单表的增删改查并学习其相关配置文件参数的配置与注意事项。
配置文件的简化
MyBatis开发环境搭建参考: MyBatis框架快速入门(一),搭建MyBatis开发环境
1.typeAliases别名处理器,简化映射配置文件中statement的parameterType,resultType属性值的编写
Mybatis的映射文件与映射器相对应,如果映射器中的方法有输入参数及返回结果,那么在Mybatis 的映射文件中也要配置与之对应的参数(parameterType)和返回结果(resultType),其属性值写全限定类名,当这个映射器有很多个方法时,我们在映射文件中配置都要以全限定类名来配置,这样配置就变得繁琐了。这时我们可以通过在核心配置文件(SqlMapConfig.xml)中使用typeAlias标签来给这些全限定类名起类型别名的方式来简化操作。
typeAliases别名处理器:为 Java 类型设置一个短的名字,可以方便我们引用某个类。
在核心配置文件(SqlMapConfig.xml)的configuration标签中添加
<typeAliases>
<!--给指定包下的每一个类创建一个默认的别名,别名就是其不区分大小写类名-->
<package name="com.mycode.domain"/>
</typeAliases>
配置后,在映射配置文件中就可以相对应的使用别名。
注意:SqlMapConfig.xml中配置顺序
Mybatis提供的别名:mybatis官方文档
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
2.在核心配置文件mappers映射器中批量注册指定包下所有的映射器接口
之前使用的方式是通过mapper逐个注册映射配置文件(配置了映射配置文件的位置,Mybatis就可以加载statement信息,再根据namespace属性找到映射器),而这种方式在有很多映射配置文件时,配置也会变得很繁琐。
通过自动注册指定包下所有的映射器接口方式简化,在核心配置文件(SqlMapConfig.xml)的configuration标签中修改mappers映射器,使用方式二:
<mappers>
<!--方式一:通过mapper逐个注册映射配置文件
配置了映射配置文件的位置,Mybatis就可以加载statement信息,再根据namespace属性找到映射器
-->
<!--<mapper resource="com/mycode/dao/UserMapper.xml"></mapper>-->
<!--方式二:自动注册指定包下所有的映射器接口
这种方式对映射配置文件的要求:映射配置文件名必须和映射器接口名相同并且在同一目录下。
Mybatis根据包名找到所有的映射器的类名和位置,符合以上两点要求就可以根据映射器的类名和位置查找同名同路径的映射配置文件。
-->
<package name="com.mycode.dao"/>
</mappers>
通过简化后在配置映射配置文件时就可以通过起别名的方式减少代码量,通过mappers映射器批量注册映射器接口的方式来简化对映射配置文件的引入注册,在此基础上通过Mybatis实现增删改查就会更为便捷。
增删改查操作
1.OGNL表达式简介
- OGNL:Object Graphic Navigator Language(对象导航图语言),是应用于Java中的一个开源的表达式语言。
- 作用:对数据进行访问,它拥有类型转换、访问对象方法、操作集合对象等功能。如从Java对象中获取某一属性的值(本质上使用的是JavaBean的getXxx()方法。如:user.getUsername()—>user.username,使用#{user.username}就可以获取userame属性的值)。
- 在#{}里、${}使用OGNL表达式,可以从JavaBean中获取指定属性的值。
2.映射配置文件中相关配置属性:parameterType与resultType说明
parameterType属性:将会传入这条语句的参数类的全限定类名或别名。
parameterType属性属性值可以是:
- 简单类型(基本数据类型或者string)
如果只有一个参数且参数是简单类型的,那么#{ }
中{}里的值可以随意写。 - JavaBean
在SQL语句中,可以在#{}
或者${}
中使用OGNL表达式来获取JavaBean的属性值。如parameterType是com.mycode.domain.User或其别名,则在SQL语句中可以使用#{username}
获取username属性值。 - JavaBean包装类(一个JavaBean里还有JavaBean)
在SQL语句中,可以在#{}
或者${}
中使用OGNL表达式来获取JavaBean的属性值。如parameterType是com.mycode.domain.MyWrapper 或其别名,则在SQL语句中可以使用#{user.username}
获取username属性值。
User类
public class User {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
User包装类
public class MyWrapper {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
resultType属性:结果集的数据要封装成的对象的全限定类名或别名。
resultType属性属性值可以是:
- 简单类型(基本数据类型或者string)
- JavaBean(JavaBean的属性名要和字段名保持一致)
当JavaBean中属性名和字段名不一致时的解决方案:在映射配置文件中配置即可
方案①sql起别名的方式
<!--JavaBean中属性名和字段名不一致的情况解决方案一-->
<select id="selectAllAliasUser" resultType="com.mycode.domain.AliasUser">
select id uid,username uname,password upassword,email uemail,birthday ubirthday from t_user;
</select>
方案②使用
resultMap
标签设置结果集中字段名和JavaBean属性的对应关系
<!--JavaBean中属性名和字段名不一致的情况解决方案二-->
<select id="selectAllAliasUser2" resultMap="aliasUserMap">
select * from t_user
</select>
<!--
resultMap标签:设置结果集中字段名和JavaBean属性的对应关系
id属性:唯一标识
type属性:结果集的数据要封装成的对象的全限定类名或别名
-->
<resultMap id="aliasUserMap" type="AliasUser">
<!--id标签:主键字段配置。 property属性:JavaBean的属性名 column属性:字段名-->
<id property="uid" column="id"></id>
<!--result标签:非主键字段配置。-->
<result property="uname" column="username"></result>
<result property="upassword" column="upassword"></result>
<result property="uemail" column="email"></result>
<result property="ubirthday" column="birthday"></result>
</resultMap>
数据库User表各字段名
JavaBean
public class AliasUser {
private Integer uid;
private String uname;
private String upassword;
private String uemail;
private Date ubirthday;
...
...
}
3.简单增删改查代码示例
①.在映射器UserMapper接口中编写增删改查方法
②.在映射配置文件UserMapper.xml中配置映射器里的每个方法
③.提供单元测试类测试
代码示例
单元测试类中使用@Before与@After注解使被@Test注解标注的public void方法执行之前执行先获取映射器接口代理对象,执行之后关闭连接。
public class CRUDTest {
private InputStream inputStream;
private SqlSession sqlSession;
private UserMapper mapper;
@Before//在使用@Test注解标注的public void方法执行之前执行
public void init() throws IOException {
//读取核心配置文件(SqlMapConfig.xml),得到输入流
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建构造者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//构造者根据输入流构造一个工厂,得到SqlSessionFactory工厂对象【构造者模式】
SqlSessionFactory factory = builder.build(inputStream);
//工厂对象生产一个SqlSession对象【工厂模式】
sqlSession = factory.openSession();
//使用SqlSession对象获取映射器UserMapper接口的代理对象由代理对象完成功能【代理模式】
mapper = sqlSession.getMapper(UserMapper.class);
}
@After//在使用@Test注解标注的public void方法执行之后执行
public void destory() throws IOException {
sqlSession.close();
inputStream.close();
}
}
- 在映射器UserMapper接口中编写
增加
功能
public interface UserMapper {
//增加用户
int insertUser(User user);
}
- 映射配置文件中添加 insertUser方法的配置
<!--
parameterType属性:将要传入语句的参数的完全限定类名或别名
-->
<insert id="insertUser" parameterType="user">
<!--对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis提供了一种方法来生成主键:
selectKey标签:用于在添加数据之后,查询最新主键值
keyProperty属性:查询得到的最新主键值,放在参数user的哪个属性里
resultType属性:查询的最新主键值,是什么类型。(MyBatis允许任何简单类型用作主键的类型,包括字符串)
order属性:是在添加之前查询最新主键值,还是在添加之后查询最新主键值。MySql设置为AFTER Oracle设置为BEFORE
标签体:查询最新主键值的SQL语句
【扩展】:当插入数据后就要在界面显示该数据时,应主键(id)是自增的,在插入时并没有赋值插入,所以这时如果直接输出显示的id就为null,
这时可以通过配置映射配置文件(UserMapper.xml)selectKey标签来查询id的值,并返回显示。具体查看测试类
-->
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into t_user (id,username,password,email,birthday) values (#{id},#{username},#{password},#{email},#{birthday});
<!-- SQL语句中使用了OGNL表达式,#{}代表占位符,相当于预编译对象的SQL中的?,具体的值由User类中的属性确定
根据OGNL表达式这里实际应写为#{user.id}...但是Mybatis简化了操作。
-->
</insert>
- 在单元测试类中添加 增加操作方法
@Test
public void insertUserTest() throws IOException {
User user = new User();
user.setUsername("张三三");
user.setPassword("3");
user.setEmail("123456@qq.com");
user.setBirthday( new Date());
System.out.println("插入数据前"+user);
//插入数据前User{id=null, username='张三三', password='3', email='123456@qq.com', birthday=Thu Mar 12 21:19:36 CST 2020}
mapper.insertUser(user);
/*
当一个连接对象被创建时,默认情况下是自动提交事务的
从日志信息可以知道,Mybatis在创建连接后会取消自动提交事务( Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fc2b765])
而DML语句执行完一定要提交事务,所以这里需要自己手动提交事务
*/
sqlSession.commit();
/*
当插入数据后,要在界面显示该数据时,应主键(id)是自增的,没有赋值插入,所以这里输出的id就为null,
这时可以通过配置映射配置文件(UserMapper.xml)selectKey 来查询id的值,并返回显示。
*/
System.out.println("插入数据后"+user);
//插入数据后User{id=58, username='张三三', password='3', email='123456@qq.com', birthday=Thu Mar 12 21:19:36 CST 2020}
}
- 在映射器UserMapper接口中编写
删除
功能
public interface UserMapper {
//根据id删除指定用户
void deleteUser(int id);
}
- 映射配置文件中添加 deleteUser方法的配置
<!--如果只有一个参数且参数是简单类型(基本数据类型和String),那么#{}中{}里的值可以随意写,一般规范见名知意,即这里根据id查,就写为#{id} -->
<delete id="deleteUser" parameterType="int">
delete from t_user where id=#{id}
</delete>
- 在单元测试类中添加 删除操作方法
@Test
public void deleteUserTest() throws IOException {
mapper.deleteUser(35);
sqlSession.commit();
}
- 在映射器UserMapper接口中编写
修改
功能
public interface UserMapper {
//更新指定用户信息
void updateUser(User user);
}
- 映射配置文件中添加 updateUser方法的配置(改)
<update id="updateUser" parameterType="user">
update t_user set password=#{password},email=#{email} where id=#{id}
</update>
- 在单元测试类中添加 更改操作方法
@Test
public void updateUserTest() throws IOException {
User user = new User();
user.setId(58);
user.setPassword("1");
user.setEmail("123456@qq.com");
mapper.updateUser(user);
sqlSession.commit();
}
- 在映射器UserMapper接口中
根据id查询
功能
public interface UserMapper {
//根据id查询用户信息(前面查询了全部用户,这里查询单个)
User selectUserByid(int id);
}
- 映射配置文件中添加 selectUserByid方法的配置
<select id="selectUserByid" parameterType="int" resultType="user">
select * from t_user where id=#{id}
</select>
- 在单元测试类中添加 根据id查询方法
@Test
public void selectUserByIdTest() throws IOException {
User user = mapper.selectUserByid(31);
System.out.println(user);
}
- 在映射器UserMapper接口中编写
查询记录总数
功能
public interface UserMapper {
//查询总记录数
int getTotalCount();
}
- 映射配置文件中添加 getTotalCount方法的配置
<select id="getTotalCount" resultType="int">
select count(*) from t_user
</select>
- 在单元测试类中添加 查询数量方法
@Test
public void getTotalCountTest() throws IOException {
int count = mapper.getTotalCount();
System.out.println(count);
}
4.模糊查询(探究#{}和${value}的区别)
①.在映射器UserMapper接口中编写模糊查询方法
- UserMapper接口【映射器】(UserMapper)
public interface UserMapper {
//根据用户名模糊查询(使用#{}方式)
List<User> selectUserByUserName(String username);
//根据用户名模糊查询(使用${}方式)
List<User> selectUserByUserName2(String username);
}
②.在映射配置文件UserMapper.xml中配置映射器里的每个方法 与 提供单元测试类实现模糊查询
- 映射配置文件中添加 selectUserByUserName方法的配置(使用
#{}方式
模糊查询)
<select id="selectUserByUserName" parameterType="string" resultType="user">
<!--使用#{}方式进行模糊查询-->
select * from t_user where username like #{username}
</select>
- 在单元测试类中添加 模糊查询方法
//使用#{}方式进行模糊查询
@Test
public void selectUserByUserNameTest() throws IOException {
//模糊查询的条件值,需要有%
List<User> userList = mapper.selectUserByUserName("%上官%");
for (User user : userList) {
System.out.println(user);
}
}
运行结果:<××××> 为注释
DEBUG serMapper.selectUserByUserName - ==> Preparing: select * from t_user where username like ? <最终执行的SQL>
DEBUG serMapper.selectUserByUserName - ==> Parameters: %上官%(String)<参数值>
DEBUG serMapper.selectUserByUserName - <== Total: 4
User{id=4, username='上官婉儿', password='1', email='null', birthday=null}
User{id=5, username='上官锅儿', password='1', email='null', birthday=null}
User{id=6, username='上官瓢儿', password='1', email='null', birthday=null}
User{id=21, username='上官盖儿', password='1', email='null', birthday=null}
从日志中可以看出#{}
表示一个占位符,相当于预编译SQL中的?
- 可以防止SQL注入
- 使用
#{}
方式,Mybatis会自动进行参数的Java类型和JDBC类型转换(在日志中Parameters: %上官%(String
) ,带有参数类型,如是日期类型则会转换 ) - 如果 parameterType传输的是单个简单类型值,
#{}
括号中可以是 value 或其它名称。
- 映射配置文件中添加 selectUserByUserName2方法的配置(使用
${}方式
模糊查询)
<select id="selectUserByUserName2" parameterType="string" resultType="user">
<!--使用${}方式进行模糊查询 ${}的{}里必须是value-->
select * from t_user where username like '%${value}%'
</select>
- 在单元测试类中添加 模糊查询方法
//使用${}方式进行模糊查询
@Test
public void selectUserByUserNameTest2() throws IOException {
//在SQL语句中已经加了%,在测试代码中,传递参数值时不需要再加%
List<User> userList = mapper.selectUserByUserName2("上官");
for (User user : userList) {
System.out.println(user);
}
}
运行结果:
DEBUG erMapper.selectUserByUserName2- ==> Preparing: select * from t_user where username like '%上官%'
DEBUG erMapper.selectUserByUserName2 - ==> Parameters:
DEBUG erMapper.selectUserByUserName2 - <== Total: 4
User{id=4, username='上官婉儿', password='1', email='null', birthday=null}
User{id=5, username='上官锅儿', password='1', email='null', birthday=null}
User{id=6, username='上官瓢儿', password='1', email='null', birthday=null}
User{id=21, username='上官盖儿', password='1', email='null', birthday=null}
从日志中可以看出${value}
表示拼接SQL串,相当于把实际参数值,直接替换掉${value}
- 不能防止SQL注入
- 使用
${}
方式,Mybatis不进行参数的Java类型和JDBC类型转换(拼接的方式是直接把参数“这个时间字符串”拼接上去了) - 如果 parameterType传输的是单个简单类型值,
${}
括号中只能是 value。
基于#{}和${value}的区别与特点,在实际使用中,出于安全性考虑,一般都使用#{}的方式。但是有一种情况,当我们要对查询结果按照一定的顺序进行排序时,使用#{}的方式是达不到预期效果
的。
如:根据指定字段名(id)降序查询所有用户
- UserMapper接口【映射器】(UserMapper)
public interface UserMapper {
//根据指定字段名(id)降序查询所有用户(使用#{}方式)
List<User> selectAllUserByIdSort(String columnName);
//根据指定字段名(id)降序查询所有用户(使用${}方式)
List<User> selectAllUserByIdSort2(String columnName);
}
- 映射配置文件中添加 selectAllUserByIdSort方法的配置(使用
#{}方式
)
<select id="selectAllUserByIdSort" parameterType="String" resultType="User">
select * from t_user order by #{value} desc
</select>
- 在单元测试类中添加 降序查询方法
@Test
public void selectAllUserByIdSort() throws IOException {
List<User> userList = mapper.selectAllUserByIdSort("id");
for (User user : userList) {
System.out.println(user);
}
}
运行输出:(无效)
[com.mysql.jdbc.JDBC4Connection@4cf4d528]
DEBUG erMapper.selectAllUserByIdSort - ==> Preparing: select * from t_user order by ? desc
DEBUG erMapper.selectAllUserByIdSort - ==> Parameters: id(String)
DEBUG erMapper.selectAllUserByIdSort - <== Total: 40
User{id=1, username='张三', password='1', email='null', birthday=null}
User{id=2, username='李四', password='1', email='null', birthday=null}
User{id=3, username='王五', password='1', email='null', birthday=null}
User{id=4, username='上官婉儿', password='1', email='null', birthday=Fri Mar 13 00:00:00 CST 2020}
User{id=5, username='上官锅儿', password='1', email='null', birthday=null}
User{id=6, username='上官瓢儿', password='1', email='null', birthday=null}
......
.......
- 映射配置文件中添加 selectAllUserByIdSort2方法的配置(使用
${}方式
)
<select id="selectAllUserByIdSort2" parameterType="String" resultType="User">
select * from t_user order by ${value} desc
</select>
- 在单元测试类中添加 降序查询方法
@Test
public void selectAllUserByIdSort2() throws IOException {
List<User> userList = mapper.selectAllUserByIdSort2("id");
for (User user : userList) {
System.out.println(user);
}
}
运行输出:
DEBUG rMapper.selectAllUserByIdSort2 - ==> Preparing: select * from t_user order by id desc
DEBUG rMapper.selectAllUserByIdSort2 - ==> Parameters:
DEBUG rMapper.selectAllUserByIdSort2 - <== Total: 36
User{id=43, username='百里', password='3', email='123456@qq.com', birthday=null}
User{id=39, username='迪迦', password='1', email='null', birthday=null}
User{id=38, username='伽罗', password='1', email='null', birthday=null}
User{id=37, username='小乔', password='1', email='null', birthday=null}
User{id=36, username='大乔', password='1', email='null', birthday=null}
...
...
源码简单分析通过代理对象的方式实现增删改查的运行过程
功力不够,简单分析
以执行更新操作为例
读取核心配置文件SqlMapConfig.xml(通过类加载器读取SqlMapConfig.xml,得到输入流)
创建构造器
构造者根据输入流读取配置文件,解析xml,把所有配置信息封装到Configuration对象,构造一个DefaultSqlSessionFactory工厂并把Configuration传递给工厂【构造者模式】
工厂生产一个DefaultSqlSession出来,并把Configuration传递给DefaultSqlSession【工厂模式】
使用DefaultSqlSession对象获取映射器UserMapper接口的代理对象
单元测试类中让代理对象去操作数据
代理对象的行为
在Configuration封装配置文件的信息中,映射器配置文件UserMapper.xml中namespace属性值(com.mycode.dao.UserMapper)与每个statement的id属性值(即每个方法名)作为key,把sql语句与参数结果集(value)等保存在一个Map中。
在单元测试类中通过所测试的方法( 映射器全限定类名 + 方法名)作为key值,得到对应sql语句与参数结果集(value值),即得到了要执行的sql语句与参数或结果集。
接下来的就是Mybatis底层封装JDBC执行sql的操作了