学习Mybatis的第一天
今天主要学习了Mybatis的一些入门操作,首先是知到了什么是Mybatis,它的功能的什么,使用Mybatis的简单步骤,sql语句的映射文件,Mybatis的核心配置文件,java代码中的一些API对象……
文章目录
Mybatis的概念
它是持久化成的一个框架,它帮我门封装了元素jdbc繁琐的操作,就比如加载驱动,获取连接对象,获取执行sql对象,封装查询结果。Mybatis让我们只注重于sql语句的编写。
就我目前的理解,JdbcTemplate也能完成这些事请,但Mybatis将sql语句的java代码解耦合了,JdbcTemplate的sql语句还是和持久化成的java代码在一起。
入门的基本步骤
- 首先导入数据库的mysql-connector-java 和 Mybatis 的坐标
- 创建数据表 还有和数据表对应的实体类对象
- 创建sql语句的映射文件
- 创建Mybatis核心配置文件
- java代码运行
创建sql语句的映射文件
首先是命名规范,如果是maven工程,在resources文件夹下创建一个一个dao一样的目录,然后创建一个XXXMapper.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 表示命名空间,在该标签的标签体中创建insert、delete、update、select标签用来配置sql语句,这些标签需要id属性,还需要根据sql语句的情况来添加parameterType和resultType。
这是因为如果是查询某些数据的话,你要告诉Mybatis这些查询到的数据要封装成哪个类。然后我们再获取封装好的对象。
如果sql是需要几个参数来进行条件查询或修改的话,我们就需要一个parameterType来传一个参数类型。以前占位符的写法是使用问号?来占位,然后我们在为这些问号赋值,Mybatis不能使用? 而需要使用#{类的属性名},注意大括号里面一定要和parameterType定义类的属性名一致,不然Mybatis不知道你的那些参数要放在那个位置。但如果parameterType不是一个实体类 而是只有一个参数的情况,那#{}这个里面就可以随便写名字。
创建Mybatis的核心配置文件
首先是在resources文件夹下创建一个Mybatis的核心配置文件,在这个配置文件中也要复制粘贴两行约束
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
核心配置文件的根标签是configuration,它的下面有很多子标签,入门阶段主要用来两个,一个是environments是用来配置数据源的,还有一个是mappers是用来加载sql语句的映射文件的。
在environments中可以配置多个数据源,它有一个default属性用来设置默认的数据源,environments标签下使用environment标签配置具体的数据源,可以为encironment标签设置一个id属性,将这个名字放入environments的default属性中表示默认使用这个数据源的环境。
encironment标签体中还有两个标签,一个是transactionMagger设置事务管理器,还有一个dataSource标签设置数据源的
<configuration>
<environments default="developement">
<environment id="developement">
<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
事务管理器,常用的类型也就是JDBC 它的含义是接下来使用的数据库连接对象都是从下面配置的池中获取。还有一个就是MANAGED,他让容器来管理事务的整个生命周期。
数据源一般使用的是POOLED 表示使用数据库连接池,还有UNPOOLED 和JNDI
mappers标签的加载sql语句的映射文件的,
<mappers>
<mapper resource="映射文件地址"></mapper>
</mappers>
映射文件的地址也有多种写法,一般是直接写在resources资源文件夹下的路径。还有的写法是写一个完整的URL 、使用映射器接口实现类的完全限定类名class、将包内的映射器接口实现全部注册为映射器name。
在configuration标签体中还有几个常用的标签
properties 引入其他properties文件
typeAliases 为自定义的全类名起别名,Mybatis
java测试部分
java代码的基本步骤就是
- 加载Mybatis的核心配置文件
- 创建SqlSession工厂构建类
- 获取SqlSession工厂类
- 从SqlSession工厂类中获取SqlSession对象
- 使用SqlSession调用增删改查四类方法进行执行sql
- 如果获取的SqlSession是不自动提交事件的话还需要我们手动提交事务
- 释放SqlSession对象。
代码大致如下:
InputStream is = Resources.getResourecsAsStream("核心配置文件名");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(boolean 决定SqlSession是否会自动提交事务)
sqlSession.update("Mappers标签namespace.Mapper标签的id",Mapper标签中属性parameterType或resultType类型的对象)
sqlSession.commit();
sqlSession.close();
SqlSessionFactoryBuilder类是用来构建SqlSessionFactor类的
SqlSessionFactory是用来为我们提供SqlSession对象的
SqlSession对象是执行sql和事务操作的。
执行sql语句的方法
第一种是直接在dao层中创建接口+实现类,在实现类中创建每个sql语句对应的方法,通过SqlSession对象.select、insert、delete、update(“sql语句的映射” , 参数)。
还有一种是直接创建dao层的接口,在接口中创建sql语句对应的方法,然后可以在业务层通过SqlSession对象的getMapper(接口名.class)方法获取接口的代理对象,代理对象再调用接口中的方法,接口中的方法与映射文件中的sql语句有映射关系,可以调用接口中的方法,然后直接指向sql语句。但有一些前提条件:
<mapper>
标签中的namespace属性为接口的全类名<select或delete或insert或update>
标签中的id为接口中的方法名- parameterType属性要与方法的形参对应
- resultType属性与方法的返回值对应
代码如下:
- 定义一个接口
public interface UserMapper {
public User findUserById(int id);
}
- 映射文件
<mapper namespace="com.XXX.dao.UserMapper">
<select id="findUserById" parameterType="int" resultType="com.XXX.domain.User">
select * from user where id=#{id}
</select>
</mapper>
- java测试代码
@Test
public void test6() throws IOException {
//加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获得持久化层接口代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//执行方法
User user = userMapper.findUserById(1);
//输出
System.out.println(user);
//关闭
sqlSession.close();
}
这两种方法是主要区别就在与我自己是否手动编写了接口实现类。
映射文件的其他技术
动态sql
在日常的开发中,有很多时候我们的sql语句是没办法确定的,我们需要根据传递过来的参数的多少来决定sql语句的编写,就比如分页查询数据,有时候可能直接查询某页的多少数据,有时候也有可能使用模糊查询,查询某内容的某页数据。这就需要使用到<if>
标签了。
使用如下所示:
<!-- 例如 根据user的某些属性来查询数据 -->
<mapper namespace="user">
<select id="findAll" parameterType="com.XXX.XXX.User" resultType="com.XXX.XXX.User">
select * from user
<where>
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
</select>
</mapper>
主要就是在if标签的test属性中判断某个属性的值是否存在,如果存在就在原sql语句中添加一个查询条件。
还有foreach
标签。该标签的使用没有if
标签的使用次数多,它主要是用来查询比如id=1 或者是 id=2 或者是id=3 ……的用户 。具体使用如下:
<!-- 查询user 通过动态sql的foreach标签查询 -->
<!-- 原本SQL select * from user where id in(1,2,3) -->
<!-- foreach标签属性介绍 collection就是传过来的参数 open是以什么开始 close是以什么结束
item就是起的一个变量名 遍历集合时用这个名字 separator是sql参数之间的分割 -->
<select id="findByIds" parameterType="list" resultType="com.hnyyjsxy.domain.User">
select * from user
<where>
<foreach collection="list" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
sql片段的抽取
<!-- sql的抽取-->
<sql id="selectUser">select * from user</sql>
使用如下:
<!-- 查询所有user -->
<select id="findAll" resultType="com.XXX.domain.User">
<include refid="selectUser"/>
</select>
映射文件的总结
在sql的映射文件中 目前共使用了以下标签
- mapper ------>标签体
- insert ------>插入相关sql
- delete ------>删除相关sql
- update ------>修改相关sql
- select ------>查询相关sql
- if ------>动态sql语句条件判断标签
- where ------>动态sql语句 不需要我们在sql语句中写where
- foreach ------>sql语句的动态添加
- sql ------>sql片段的抽取
配置文件的其他技术
类型转换 typeHandlers标签
mybatis框架已经自动帮我们实现了一些常见的java和数据库之间的类型转换。就比如我们的POJO类和parameterType属性、数据库查询结果帮我们封装resultType属性、java中的String 和 数据库之间是varchar 等等。但还有一些情况下,它提供的类型转换不能够满足我们业务的需要,这就需要自定义类型转换了。
我们可以实现org.apache.ibatis.type.TypeHandler接口,或者是继承org.apache.ibatis.type.BaseTypeHandler 类,后面这个类是实现了前面的接口,然后选择性的将它映射到JDBC类型中。
开发步骤:
①定义转换类继承类BaseTypeHandler<T>
②覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法
③在MyBatis核心配置文件中进行注册
public class MyDateTypeHandler extends BaseTypeHandler<Date> {
//将java类型转换成数据库需要的类型
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType type) {
preparedStatement.setString(i,date.getTime()+"");
}
//将数据库中的数据转换成java对象
//参数中ResultSet查询结果集 String 就是数据库中要转换的字段的名称
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
//获取结果集中我需要的数据
return new Date(resultSet.getLong(s));
}
//将数据库中的数据转换成java对象
//产生中int是字段的位置
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
return new Date(resultSet.getLong(i));
}
//将数据库中的数据转换成java对象
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return callableStatement.getDate(i);
}
}
在核心配置文件中配置代码如下:
<!--注册类型自定义转换器-->
<typeHandlers>
<typeHandler handler="com.itheima.typeHandlers.MyDateTypeHandler"></typeHandler>
</typeHandlers>
使用第三方插件 plugins标签
mybatis可以使用第三方插件来对功能进行扩展,这里就拿分页查询的插件来举例。
开发步骤:
①导入通用PageHelper的坐标
<!-- 分页查询的插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<!-- 解析器 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
②在mybatis核心配置文件中配置PageHelper插件
<!-- 配置分页查询的插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
③测试分页数据获取
/**
* 查询全部学生数据 添加了分页查询插件后 查询分页的几条数据
* @throws Exception
*/
@Test
public void test9() throws Exception{
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
//设置分页相关参数 当前页+每页显示条数 下面就是第一页 每页显示4条
PageHelper.startPage(1,4);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.findAll();
for (User user : userList) {
System.out.println(user);
}
//其他分页的数据 首先创建一个pageInfo对象 泛型是查询到的结果类型 构造方法中传的是查询结果集合
PageInfo<User> pageInfo = new PageInfo<User>(userList);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("上一页:" +pageInfo.getPrePage());
System.out.println("下一页:" +pageInfo.getNextPage());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
sqlSession.close();
}
核心配置文件总结
在mybatis的核心配置文件中,目前共使用了如下几种标签
标签 | 说明 |
---|---|
configration | 配置文件的根标签 |
properties | 加载外部properties文件 |
typeAliases | 设置类型别名 |
environments | 数据源环境配置 |
typeHandlers | 自定义类型转换 |
plugins | 加载插件 |
mappers | 加载映射文件 |
多表查询
多表查询的表结构一般有 一对一 一对多 多对多
一对一
- 首先在实体类中1添加一个属性,用来存放实体类2的数据。
- 在持久层的接口中创建相应的方法
- 映射文件的编写,主要内容如下:
我觉得多表查询无非就是多查询了几个字段的数据,问题在于如果告诉mybatis这几个字段我是需要封装到实体类2 中。解决方法就是<ResultMap>
标签
<mapper namsepace="接口全类名">
<resultMap id="自己随便起名" type="将结果封装为哪个类的全类名">
<!-- 主键一般使用id标签 其他的使用result标签 -->
<id column="数据库字段" property="实体类1属性名"/>
<result column="数据库字段" property="实体类1属性名"/>
……
<!-- 接下里封装的数据是实体类2的数据 这里需要在核心配置文件中为实体类2起别名
我这里试了一下,如果直接使用全类名.属性名就会报错
我看的教学视频也没讲太清楚 我也纠结过这下面的到底是别名还是实体类1中定义实体类2的属性名
之后我也测试了一下,修改了属性名 可以正常运行 如果修改了别名 就报错 所以这下面的别名
-->
<result column="数据库字段" property="实体类2别名.属性名"/>
</resultMap>
<select id="接口中方法名" resultMap="上面定义的resultMap标签id">
多表查询的sql语句
</select>
</mapper>
还有一种写法
<mapper namespace="接口全类名">
<resultMap id="自己随便起名" type="将结果封装为哪个类的全类名">
<!-- 主键一般使用id标签 其他的使用result标签 -->
<id column="数据库字段" property="实体类1属性名"/>
<result column="数据库字段" property="实体类1属性名"/>
……
<!-- 接下里封装的数据是实体类2的数据 -->
<association property="实体类2的属性名" javaType="实体类2的全类名">
<id column="数据库字段" property="实体类2属性名"/>
<id column="数据库字段" property="实体类2属性名"/>
……
</association>
</resultMap>
<select id="接口中方法名" resultMap="上面定义的resultMap标签id">
多表查询的sql语句
</select>
</mapper>
一对多
- 在实体类1中创建一个属性——实体类2的集合
- 在持久化层接口定义相应的方法
- 映射文件的编写
<mapper namespace="接口全类名">
<resultMap id="随便命名" type="实体类1的全类名">
<!-- 先把实体类1的一般属性写好 -->
<id column="数据库字段" property="实体类1属性名"/>
<result column="数据库字段" property="实体类1属性名"/>
<result column="数据库字段" property="实体类1属性名"/>
<!-- 接下来配置实体类1属性中的 List<实体类2> 这是一个集合,和之前一对一就不太一样-->
<!-- property: 当前实体类1的集合属性名 ofType:集合中存储的类的类型 -->
<collection property="属性名" ofType="实体类2全类名">
<id column="数据库字段" property="实体类属性名"/>
<result column="数据库字段" property="实体类属性名"/>
<result column="数据库字段" property="实体类属性名"/>
</collection>
</resultMap>
<select id="接口中方法名" resultMap="上面定义的id">
多表查询的sql语句
</select>
</mapper>
多对多
其实多对多和一对多基本上没区别,唯一的区别就是sql语句多加了一个表。查询出来的数据进行封装和一对多没什么区别,
<mapper namespace="接口全类名">
<resultMap id="随便命名" type="实体类1的全类名">
<!-- 先把实体类1的一般属性写好 -->
<id column="数据库字段" property="实体类1属性名"/>
<result column="数据库字段" property="实体类1属性名"/>
<result column="数据库字段" property="实体类1属性名"/>
<collection property="属性名" ofType="实体类2全类名">
<id column="数据库字段" property="实体类属性名"/>
<result column="数据库字段" property="实体类属性名"/>
<result column="数据库字段" property="实体类属性名"/>
</collection>
</resultMap>
<select id="接口中方法名" resultMap="上面定义的id">
多对多查询的sql语句
</select>
</mapper>
基于注解的开发
注解的方式相对来说比较容易,而且还不需要创建专门为sql语句创建一个映射文件了。下面简单讲讲注解开发的一个基本过程吧:
- 创建持久化层接口
- 接口中定义方法,方法上面直接使用注解定义sql语句
- 在mybatis核心配置文件中加载映射关系,这里不用加载映射文件了,但是接口中的方法和sql语句其实还是存在映射关系的,不然你执行一个方法,鬼知道你要执行哪个sql语句。
<mappers>
<package name="持久化层接口所在的包名,只需要写到包即可 注意 加载映射文件和加载映射关系的语句不能同时存在"/>
</mappers>
常用注解
注解 | 说明 |
---|---|
@Insert | 用于往数据库中添加数据 |
@Delete | 删除数据 |
@Update | 修改数据 |
@Select | 查询数据 |
@Result | 对结果进行封装 |
@Results | 与@Result注解一起使用,该注解有一个数组属性,数组中存放多个@Result |
@One | 多表查询中的 实现一对一表结构数据的封装 |
@Many | 多表查询中实现一对多或多对多表结构数据的封装 |
一张表的crud
定义一个接口+多个方法,直接在方法名上面使用注解
public interface UserMapperAnno {
@Select("select * from user")
List<User> findAll();
@Insert("insert into user(username,password) values(#{username},#{password})")
void save(User user);
@Delete("delete from user where id=#{id}")
void delect(int id);
@Update("update user set username=#{username} , password=#{password} where id=#{id}")
void update(User user);
}
核心配置文件中加载映射关系
<mappers>
<package name="com.xxx.mapper"/>
</mappers>
测试类中通过SqlSession对象获取接口的代理对象,然后使用代理对象调用接口中的各个方法,执行相应的sql语句,并接收查询后的返回值。
@Test
public void test(){
InputStream is= Resoucres.getResoucreAsStream("核心配置文件名.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
接口类型 动态对象名 = sqlSession.getMapper(接口名.class);
//然后通过对象调用接口中方法实现crud
}
一对一
接口方法上面的注解需要加一点修改
public interface UserMapperAnno {
@Select("select * from 表1,表2 where 表1.id = 表2.id")
@Results({
//如果是主键可以加一个id=true的属性
@Result(id=true , column="数据库字段" ,property="实体类1属性名"),
@Result(column="数据库字段" ,property="实体类1属性名"),
@Result(column="数据库字段" ,property="实体类1属性名"),
@Result(column="数据库字段" ,property="实体类2别名.属性名"),
@Result(column="数据库字段" ,property="实体类2别名.属性名")
})
User findAll();
}
第二种方式
public interface UserMapperAnno {
@Select("select * from 表1")
@Results({
//如果是主键可以加一个id=true的属性
@Result(id=true , column="数据库字段" ,property="实体类1属性名"),
@Result(column="数据库字段" ,property="实体类1属性名"),
@Result(column="数据库字段" ,property="实体类1属性名"),
@Result(
column="数据库的字段,也就是通过那个字段去查询表2",
javaType="实体类2的全类名 表示查询到了数据封装的类型是什么样子的",
column="当前实体类要封装的属性名",
one=@One(select = "表2接口全类名.方法名")
)
})
User findAll();
}
这里还需要表2接口中定义一个根据id查询数据的方法,意思其实就是这传一个id给另一个接口中的方法,另一个接口执行然后获取返回值,将返回值封装到javaType声明类型column属性中。这里我的确不知道该怎么组织语言来表达了。
一对多
一对多 和 声明一对一其实没有太大区别 唯一区别就是@One注解该为@Many注解。也还是先查询当前表,然后通过某一个字段去查第二张表
public interface UserMapper{
@Select("select * from user")
@Results({
@Result(column="字段",property="属性名"),
@Result(column="字段",property="属性名"),
@Result(column="字段",property="属性名"),
@Result(
column="字段",
javaType="实体类2全类名",
property="属性名",
many=@Many(select = "接口全类名.方法")
)
})
List<User> findAll();
}
多对多查询
多对多的查询和一对多的查询唯一的不同就在于sql语句,它需要在查询的时候查询两张表,将中间表一起查询出来,然后用中间表的字段去查询另外一张表。也可以先查询一张表,将中间表和接口2方法上的sql一起查。
其他数据封装方式还是和一对多一致。
总结
如果是基于配置文件方式:主要就是使用如下标签:
<mapper> <select> <insert> <update> <delete> <resultMap> <result> <where> <if> <foreach> <sql>
核心配置文件中一般使用的标签有:
<configuration> <properties> <typeAliases> <typeAlias> <environments> <environment> <transactionManager> <dataSource> <typeHandlers> <plugins> <mappers> <mapper>
注解方式 常用的注解有:
@select | @update | @insert |
---|---|---|
@delete | @One | @many |