目录
一、MyBatis的DAO层实现
1.1传统实现方式
1.2接口代理实现方式
1.2.1代理开发方式简介
1.2.2代理方式实现
二、MyBatis映射文件
2.1动态sql语句
2.1.1动态sql语句概述
2.1.2静态sql语句的弊端
2.1.3动态sql之if
2.1.4动态sql之foreach
2.2SQL片段抽取
三、MyBatis核心配置文件深入
3.1typeHandlers标签
3.1.1typeHandlers概述
3.1.2默认类型处理器
3.1.3自定义类型处理器
3.2plugins标签
之前的数据库操作我们是直接写在测试类中的,在开发流程中对数据库的直接操作代码是要放在Dao层的,所以我们接下来看看如何在MyBatis中实现Dao层。
我们首先新建一个工程,配置好web环境,pom.xml中导入相关的jarb包坐标(mysql、mybatis、junit等等)。
第一步我们要编写UserDao接口,以及对应的实现类,
interface UserMapper {
public List findAll() throws IOException;
}
class UserMapperImpl implements UserMapper{
public List findAll() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");//获取核心配置文件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//获取工厂对象
SqlSession sqlSession = sqlSessionFactory.openSession();//获取sqlSession对象
List userList = sqlSession.selectList("userMapper.findAll");//通过映射文件的sql语句id找到执行的语句
return userList;
}
}
第二步我们在Service层编写测试函数,
class ServiceDemo {
public static void main(String[] args) throws IOException {
UserMapper userMapper=new UserMapperImpl();//手动创建dao层对象
List all = userMapper.findAll();
System.out.println(all);
}
}
采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是企业使用的主流。
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范:
即下图所对应的关系:
第一步我们将mapper接口的全限定名赋值给UserMapper.xml中的命名空间,
...
第二步我们让mapper接口的方法名赋值给UserMapper.xml中定义的sql语句的id,
第三步我们将mapper接口的输入参数赋值给UserMapper.xml中的parameterType类型,
第四步我们将mapper接口的输出参数赋值给UserMapper.xml中的resultType类型,
第五步我们进行测试,
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);//获取UserMapper对象
User user = userMapper.findById(1);
System.out.println(user);
}
通过这种方式,我们就可以只用写接口,然后配置接口对应的mapper映射文件,由MyBatis生成动态代理对象供我们使用。
Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
动态SQL是在运行时生成和执行SQL语句的编程方法。动态是和静态相对而言的。静态SQL指的是在代码编译时刻就已经包含在代码中的那些已经充分明确的固定的SQL语句。
首先我们来搭建一个数据库的环境,创建好实体类和对应的数据表,导入相应的jar包坐标,然后我们创建一个Mapper映射接口,里面有一个根据条件查询的方法,
public interface UserMapper {
public List findByCondition(User user);
}
接着我们对映射文件进行配置,与我们接口中的方法参数一一对应,这里where后面的条件我们先不写,
接着我们写测试类,
@Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List userList = mapper.findByCondition(new User(1, "tz", "0203"));
System.out.println(userList);
}
运行结果:
但是往往用户的数据是用户通过交互的方式进行输入的,有可能用户没有给定某个条件的限定,比如password等于什么都可以,但是此时如果我们把条件写死了,最后会得到这么一个语句,
这样查询的结果肯定是空,因为没有password等于null,而用户这样输入的本意是想让我们执行这样一条sql语句,
select * from user where id=1 and username="tz"
即对password这个属性没有限制,等于任意即可。所以这时就要用到我们的动态sql语句。
我们可以根据实体类的不同取值,使用不同的sql语句来进行查询。
比如在 id如果不为空时可以根据id查询,如果username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。我们可以在映射文件中写动态sql语句,
这样映射文件就会根据我们给的值是否存在来动态修改对应的sql语句,从而满足不同用户的需求
循环执行sql的拼接操作,例如:
SELECT * FROM USER WHERE id IN (1,2,3)
第一步我们先在接口中写一个方法,根据id数组来获取User集合,
public List findByIds(List ids);
第二步我们在映射文件中,根据接口来写查询操作,
这里foreach标签中有几个属性,
然后我们编写测试方法进行测试,
@Test//测试foreach
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List ids=new ArrayList();//创建id集合
ids.add(1);
ids.add(2);
ids.add(3);
List userList = mapper.findByIds(ids);//通过ids查找用户
System.out.println(userList);
}
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。
比如上面我们实现的两个sql语句都是从user表中选择,重复的sql语句为:
select * from user
我们可以将这句话抽取出来,给一个id,在使用的时候通过include标签给定id进行引用即可。
select * from user
MyBatis的核心配置文件是通过configuration标签进行配置的,其中有以下属性可供配置:
下面我们对typeHandlers标签和plugins标签进行深入学习。
typeHandlers的作用是处理类型转换的。
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
下表描述了一些默认的类型处理器(截取部分)。
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean, boolean | 数据库兼容的BOOLEAN |
ByteTypeHandler | java.lang.Byte, byte | 数据库兼容的NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short, short | 数据库兼容的NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long, long | 数据库兼容的NUMERIC 或 LONG INTEGER |
除了上面的默认类型处理器,我们还可以自定义类型处理器。
我们通过重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个JDBC类型。
需求:一个Java中的Date数据类型,我们想存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的毫秒值之间转换。
开发步骤:
首先我们要在User实体类中加入Date类型的变量birthday,同样数据库也要在user表中添加一个bigint类型的birthday属性。
接下来我们开始创建自定义转换器,第一步创建转换类,继承BaseTypeHandler类,
class DateTypeHandler extends BaseTypeHandler {//定义需要转换的类型
/**
* 将java类型转换为数据库需要的类型
* @param preparedStatement 执行sql语句
* @param i 执行时sql语句的要转换类型的参数下标
* @param date Date类型的日期
* @param jdbcType
* @throws SQLException
*/
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
long dateTime = date.getTime();//获取从1970年到现在的毫秒值
preparedStatement.setLong(i,dateTime);//设置下标为i的Long类型参数为dateTime,即将Date转换为Long型
}
/**
* 将数据库中的类型转换为java类型
* @param resultSet 查询的结果集
* @param s 要转换的字段名称,即这里的"birthday"
* @return Date类型日期
* @throws SQLException
*/
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
long birthday = resultSet.getLong(s);//通过属性名获得结果集中的birthday数据
Date date=new Date(birthday);//将毫秒值转换为Date类型的日期
return date;//返回日期对象
}
//将数据库中的类型转换为java类型,对前面的方法进行重载(参数不同)
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
long birthday = resultSet.getLong(i);//通过下标获得结果集中的birthday数据
Date date=new Date(birthday);//将毫秒值转换为Date类型的日期
return date;//返回日期对象
}
//将数据库中的类型转换为java类型,对前面的方法进行重载(参数不同)
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
long birthday = callableStatement.getLong(i);//获得CallableStatement中的birthday数据
Date date=new Date(birthday);//将毫秒值转换为Date类型的日期
return date;//返回日期对象
}
}
第二步对里面的方法进行重载,上面我们也已经实现了。
第三步是在MyBatis核心配置文件中进行注册(注意标签的放置位置要按照核心配置文件的顺序)。
第四步我们进行测试,
@Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user=new User(4,"test","123",new Date());
userMapper.save(user);
sqlSession.commit();//提交事务
sqlSession.close();//关闭会话连接
}
可以看到数据已经成功插入到数据库中了,接下来我们来从数据库中读取该数据,
首先我们在接口中定义好查询的方法,
public User findById(int id);
然后我们在映射文件中对该方法配置好对应的sql语句,
最后我们进行测试,
@Test//查询数据
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findById(4);//查找id为4的user数据
System.out.println(user.getUsername()+"的生日:"+user.getBirthday());//打印用户的生日
sqlSession.commit();//提交事务
sqlSession.close();//关闭会话连接
}
plugins标签即插件标签,是用来扩展MyBatsi功能的。
MyBatis可以使用第三方的插件来对功能进行扩展,例如分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。我们以PageHelper为例来看看如何使用plugins标签。
假如我们数据库中有很多用户数据,有一个查询所有用户的方法,
我们希望能让这些数据分页展示,接下来我们看看如何使用PageHelper进行分页。
使用分页助手PageHelper的开发步骤如下:
第一步我们导入jar包坐标,
com.github.pagehelper
pagehelper
3.7.5
com.github.jsqlparser
jsqlparser
0.9.1
第二步我们在mybatis核心配置文件中配置PageHelper插件,
第三步我们进行测试,
@Test//分页查询所有用户信息
public void test4() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
PageHelper.startPage(2,3);//设置分页的相关参数(当前页和每页显示的条数,这里为第2页,所以从第第4条数据开始,显示3条用户信息4,5,6)
List userList = userMapper.findAll();//查找所有用户
for (User user : userList) {
System.out.println(user);
}
sqlSession.commit();//提交事务
sqlSession.close();//关闭会话连接
}
我们还可以通过PageInfo对象获取分页的相关信息,
@Test//获取分页相关的参数
public void test5() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
PageHelper.startPage(2,3);//设置分页的相关参数(当前页和每页显示的条数)
List userList = userMapper.findAll();//查找所有用户
for (User user : userList) {
System.out.println(user);
}
PageInfo pageInfo=new PageInfo(userList);//利用查询出来的数据逆向获得分页的相关信息
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示用户数:"+pageInfo.getPageSize());
System.out.println("用户总数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("上一页:"+pageInfo.getPrePage());
System.out.println("下一页:"+pageInfo.getNextPage());
System.out.println("是否为第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否为最后一页:"+pageInfo.isIsLastPage());
sqlSession.commit();//提交事务
sqlSession.close();//关闭会话连接
}