春招在即,希望看到博客的你,也要加油努力;就像《当幸福来敲门》中男主人公那样,幸福可能会迟到,但绝不会缺席;正是百花齐放的季节,我们也要去争那一片红!
一、mybatis入门
1、mybatis入门概述和环境搭建
1.1 什么是框架
- 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。
简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别
人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。
**框架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层**
。
1.2 框架要解决的问题
- 框架要解决的最重要的一个问题是技术整合的问题,在 J2EE 的 框架中,有着各种各样的技术,不同的软件企业需要从 J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。
1.3 软件开发的分层重要性
- 框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的
MVC 软件设计思想就是很好的分层思想。
1.4 分层开发下的常见框架
- Mybatis:解决数据的持久化问题的框架,就是和数据库打交道
- Spring MVC:解决 WEB 层问题的 MVC 框架
- Spring:解决技术整合问题的框架
2、mybatis的入门案例
2.1 MyBatis 框架概述
- mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
- mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并
返回。 - 采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。为了我们能够更好掌握框架运行的内部过程,并且有更好的体验
ORM: Object Relational Mappging 对象映射关系
简单地说:就是把数据库表和实体类的属性对应起来,让我们操作实体类就操作数据库表!
2.2 MyBatis 的创建和使用
项目目录:
- 1、创建 maven 工程
- 2、添加 Mybatis的坐标(日志、数据库连接、单元测试等!)
<!--打包方式:jar包-->
<packaging>jar</packaging>
<!--mybatis坐标:导入依赖-->
<dependencies>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--mybatis配置-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!--log4j日志文件-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
- 3、创建数据库和表
- 4、编写 User 实体类
注意:实体类的属性必须和表字段名保持一致
/**
* 实体类:属性和字段名与数据库表的属性和字段名保持一致
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
/*
** 添加get,set,toString方法 **
*/
- 5、编写持久层接口 IUserDao
持久层接口里面定义的就是我们要执行的方法,下面的:findAll() 方法,我们会把查询到的所有user对象封装到 list集合并返回
/**
* IUserDao 接口就是我们的持久层接口(也可以写成 UserDao 或者 UserMapper)
*/
public interface IUserDao {
/**
* 查询所有操作
* @return
*/
List<User> findAll();
}
- 6、编写持久层接口的映射文件 IUserMapper.xml
注意:
1、mapper标签中的 namespace 属性写的是:使用的 dao 接口的全限定类名;2、select 标签里面的 id 属性 填写的是:要用的 dao 接口下的方法名;3、resultType 标签:这里填写的内容是告诉 mybatis 框架,执行 findAll 方法后的结果以 user 对象的形式返回(每查询一条记录给封装成一个user对象),那我们就把 User 实力类的全路径填写到这里就ok了
<?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">
<!--dao的全限定类名-->
<mapper namespace="com.My.dao.IUserDao">
<!--配置查询所有 id:dao里面要执行的方法名-->
<select id="findAll" resultType="com.My.domain.User">
select * from user
</select>
</mapper>
- 7、编写SqlMapconfig.xml 配置文件
<?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">
<!--mybatis的主配置文件-->
<configuration>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置四个基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,配置映射文件的是每个 dao 独立的配置文件-->
<mappers>
<mapper resource="mapper/IUserMapper.xml"></mapper>
</mappers>
</configuration>
- 8、导入配置文件 log4j.properties
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
- 9、编写测试类
public class MybatisTest {
public static void main(String[] args) throws IOException {
//1、读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapconfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3、使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4、使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5、使用代理对象执行方法
List<User> list = userDao.findAll();
for (User user:list){
System.out.println(user);
}
//6、释放资源
session.close();
in.close();
}
}
3、了解mybatis执行的细节
- 上面我们简单的了解到了 mybatis的基本使用,下面我们对 mybatis 中的中实现原理进行分析,然后再自定义mybatis框架
1、设计模式分析
- 一、读取配置文件:绝对路径和相对路径读取,都会遇到一些问题
1、绝对路径:d:/xxx/xxx.html 例如绝对路径下,从d盘读取xxx.html文件,那我们的机器没有d盘怎么办?
2、相对路径:src/java/main/xxx.html 我们知道web工程一部署,src文件夹就没了,那也没办法获取路径
所以在我们实际开发中,上述两个方式基本都不用,我们用的是下面两种方式:使用类加载器、使用ServletContext对象中的getRealPath()
1、使用类加载器读取配置文件,它只能读取类路径下的配置文件,在我的这篇博客:Java JDBC使用步骤详解就是通过类加载器得到resources目录下配置文件
2、使用ServletContext对象中的getRealPath(),得到当前应用部署的绝对路径,这个路径就是你项目在哪,路径就是哪里
说明: 下图是我用tomcat搭建网站项目时候刚好用到了getRealPath();后面我会把此项目更新到博客!
- 二、构建者模式
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
创建工厂 mybatis 使用了构建者模式
,假设我们现在要建一个工厂,生活中,我们要找工人,选址,签合同什么的很多步骤;但是现在不用了,我们有了一个包工队:builder,我们只需要把钱:in 给它,它就把把工厂:factory 建好
- 三、工厂模式
//3、使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
生产SqlSession使用了工厂模式,这样会避免一个问题,假设我们现在有一个实现类:SqlSession session = new 1实现(),当我们哪天实现变了,我们又改:SqlSession session = new 2实现(),我们知道我们的应用都是 web开发,都是部署在服务器上,那我们每次修改源码,都要重新部署,重新编译;所以使用 工厂模式,我们把 new 这个关键字给屏蔽了,解耦(降低类之间的依赖关系)!
- 四、代理模式
//4、使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
创建 Dao 接口实现类使用了代理模式,我们可以不修改源码基础上对已有方法增强
mybatis在使用代理dao的实现增删改查 只做了两件事:创建代理对象和在代理对象中调用 selectList
;
2、执行查询分析
我们来简单解读下上面这幅图:我们先有了连接数据库的信息,才可以获取到 Connection 对象,然后通过:
<!--指定映射配置文件的位置,配置映射文件的是每个 dao 独立的配置文件-->
<mappers>
<mapper resource="mapper/IUserMapper.xml"></mapper>
</mappers>
我们就可以得到配置映射的文件 IUserMapper.xml:
<!--dao的全限定类名-->
<mapper namespace="com.My.dao.IUserDao">
<!--配置查询所有 id:dao里面要执行的方法名-->
<select id="findAll" resultType="com.My.domain.User">
select * from user
</select>
</mapper>
重点来了:
在配置映射文件我们可以得到两个重要的信息 1、实体类的全限定类名 2、sql语句;有了实体类的全限定类名我们就可以把执行 sql 语句后的结果;因为表的列名和实体类的属性名是一样的,我们通过反射的方式根据名称获取每个属性,并把值给进去
为了让上图右侧的:selectList方法执行,我们要提供什么呢?
1、连接信息
2、映射信息:执行的sql语句、封装结果的全限定类名 我们把这两个信息封装成一个对象 Mapper
,怎么存呢,我们选择的是 键值对存储的 Map集合,那么key必须是唯一的,为此我们选择dao的限定类名+方法名的形式作为key,这里就是:com.My.dao.IUserDao.findAll
3、创建代理对象分析
//4、使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
我们写个伪代码分析下这个 getMapper方法
二、mybatis基本使用
- 说明:项目还是上面我们所建的项目:
1、mybatis的单表CRUD操作
1、保存操作
- 1、在 dao 包下的 IUserDao 接口 写保存方法
/**
* 增
*/
void saveUser(User user);
- 2、在 IUserMapper.xml 映射文件进行配置
<!--增 parameterType:参数类型-->
<insert id="saveUser" parameterType="com.My.domain.User">
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>
- 参数说明:id后面填写的就是 dao接口下要执行的方法名、parameterType 填写的就是我们的参数类型,这里我们参数类型为 User,也就是我们实体类的类名、#{xxx}:mybatis格式要求,把当前参数(user) 的属性名传进来,和前面的 表的字段名进行匹配
补充:
如果我们想在插入当前数据后,查看当前id是多少,怎么做,mybatis给我们提供了 selectKey标签 :
<!--配置插入数据后 获取插入数据的id keyProperty:id的属性名 对应的是实体类
keyColumn:id的列名 对应的是表 order:什么时候执行获取id,这里是在我们执行插入语句之后-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
- 参数说明 keyProperty:id属性名,对应的是实体类的id、keyColumn:id的列名,对应的是表的列名、 resultType:结果类型 、order=“AFTER”:插入语句后执行(获取id值)
所以我们的映射配置更新为:
<!--增 parameterType:参数类型-->
<insert id="saveUser" parameterType="com.My.domain.User">
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
<!--配置插入数据后 获取插入数据的id keyProperty:id的属性名 对应的是实体类
keyColumn:id的列名 对应的是表 order:什么时候执行获取id,这里是在我们执行插入语句之后-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
</insert>
- 3、测试:
公用:
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before
public void init() throws Exception{
//1、读取配置文件
in = Resources.getResourceAsStream("SqlMapconfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3、使用工厂生产SqlSession对象
sqlSession = factory.openSession();
//4、使用SqlSession创建Dao接口的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception{
sqlSession.commit();
//释放连接
in.close();
sqlSession.close();
}
- 说明:为什么我们要在 destroy() 方法中添加 sqlSession.commit() 手动提交事务
回答:如果我们不手动提交事务,在我们执行增删改操作时候,程序虽然没有报错,但是运行结果有以下几行代码,1、关闭了自动提交事务,2、事务回滚,那就导致了我们的增删改操作白执行了,不会成功。所以,我们在执行增删改操作时候,必须手动的提交事务!
1、Setting autocommit to false on JDBC Connection
2、Rolling back JDBC Connection
保存:
@Test
public void testSave(){
//2、插入数据
User user = new User();
user.setUsername("唐门");
user.setAddress("北京");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("保存操作之前:"+user);
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
/**
* Setting autocommit to false on JDBC Connection :关闭自动提交事务,如果不手动提交事务,那么底下:
* Rolling back JDBC Connection 事务回滚,导致我们的操作失败
*/
//提交事务 ,我们可以把事务提交放在资源释放之前,代码简洁
//sqlSession.commit();
}
2、更新操作
- 说明:执行流程和上面一样,我这里就不写流程了
- 1、更新方法:
/**
* 改
*/
void updateUser(User user);
- 2、映射配置
<!--更新-->
<update id="updateUser" parameterType="com.My.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>
- 测试
@Test
public void testUpdate(){
//3、更新
User user = new User();
user.setId(50);
user.setUsername("update");
user.setAddress("北京");
user.setSex("女");
user.setBirthday(new Date());
userDao.updateUser(user);
}
3、删除操作
- 1、删除方法
/**
* 删
*/
void deleteUser(Integer id);
- 2、映射配置
<!--删 parameterType:这里参数类型写 int,Interger,java.lang.Interger都可以的-->
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}; <!--后面的#{id} id只代表占位符,没有特殊含义随便起名 可以理解为形参,就是代表的是dao接口中
deleteUser 方法的参数 id -->
</delete>
- 说明:1、parameterType参数类型这里,我们可以写:int、Interger、java.lang.Interger 因为mybatis框架给 整型参数类型起的别名(外号),这几个都是一样的;2、上面:id=#{id} 中 后面的#{id} 中的id 仅仅为一个形参,实参为 我们 deleteUser 方法传入的参数
@Test
public void testDelete(){
//4、删
userDao.deleteUser(48);
}
- 3、测试
@Test
public void testDelete(){
//4、删
userDao.deleteUser(48);
}
3、查询操作
- 根据id查询用户信息
- 1、查询方法
/**
* 查
*/
User findById(Integer id);
- 2、映射配置
<!--查 因为我们的返回值是个user对象,所以我们需要resultType来告诉mybatis把结果封装到哪里去-->
<select id="findById" parameterType="int" resultType="com.My.domain.User">
select * from user where id=#{id};
</select>
- 3、测试:
@Test
public void testfindById(){
//5、查
User user = userDao.findById(50);
System.out.println(user);
}
4、模糊查询
- 根据 username字段查询 用户信息
- 1、查询方法:
/**
* 根据名称模糊查询
*/
List<User> findByName(String name);
- 2、配置映射:我们有两种配置方式
#{} 和 ${}
,我们分别运行,根据结果来得出结果
#{}:
<!--根据名称模糊查询-->
<select id="findByName" parameterType="String" resultType="com.My.domain.User">
select * from user where username like #{username};
</select>
- 测试:
@Test
public void testfindByName(){
//6、模糊查寻
List<User> list = userDao.findByName("%王%");
System.out.println(list);
}
${}:
<!--根据名称模糊查询-->
<select id="findByName" parameterType="String" resultType="com.My.domain.User">
select * from user where username like '%${value}%';
</select>
- 测试
@Test
public void testfindByName(){
//6、模糊查寻
List<User> list = userDao.findByName("王");
System.out.println(list);
}
#{} 和 ${} 的区别
- 1、#{}采用的是预编译处理,${} 是字符串替换
- 2、mybatis 在处理 #{}时候,会将#{}替换为 ?号,调用 PreparedStatement的set方法来赋值
- 3、mybatis在处理 ${} 时候,就是把 ${}替换成变量的值
- 4、使用 #{}可以有效地防止 SQl 注入,因为预编译机制:SQL的结构已经固定,即使用户输入非法参数,也不会对SQL语句结构产生影响
- 补充:一般的, #{}用来传字段名,动态的进行排序等!
5、使用聚合函数
- mybatis也支持使用聚合函数,查询返回一行一列
- 我们举个例子:查询用户记录条数
- 1、方法
/**
* 获取用户总记录条数
*/
int findTotal();
- 2、映射配置
<!--获取用户总记录条数-->
<select id="findTotal" resultType="int">
select count(id) from user ;
</select>
- 3、测试
@Test
public void testfindTotal(){
//6、模糊查寻
int count = userDao.findTotal();
System.out.println(count);
}
2、mybatis的参数和返回值
2.1 parameterType输入类型
1、传递简单类型
2、传递 pojo对象(也就是javabean 实体类对象):mybatis使用 ognl 表达式解析对象字段的值,#{}或者${}括号中的值为 pojo 属性名称
简单说下 ognl表达式:它是通过对象的取值方法。在写法上把 get 给省略了 比如:获取用户名称 :user.username;因为mybatis中,我们在映射文件中配置了全限定类名,所以我们就不用写user,直接写 username
3、传递 pojo对象的包装对象
开发过程中,我们的查询条件是一个综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如:用户购买商品的信息),这时候我们就可以使用包装对象传递输入参数。pojo类中包含pojo
- 栗子: 根据用户名查询信息,查询条件方到 QueryVo 的 user属性中
- 1、QueryVo 实体类
pojo 类User就成为我们pojo类QueryVo 的一个属性(作为查询条件)
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
- 2、查询方法
/**
* 根据QueryVo中的查询条件查询用户
* @return
*/
List<User> findUserByVo(QueryVo vo);
- 3、映射配置
注意:parameterType参数不再是 User 实体类,而是QueryVo实体类;QueryVo实体类里面,我们把 User 实体类作为属性,也就是查询条件,所以 下面: #{user.username} 就是根据 User 实体类中的 username 属性来查询!
<!--根据QueryVo的条件查询用户-->
<select id="findUserByVo" parameterType="com.My.domain.QueryVo" resultType="com.My.domain.User">
select * from user where username like #{user.username};
</select>
- 4、测试
@Test
public void testByVo(){
QueryVo vo = new QueryVo();
User user = new User();
vo.setUser(user);
user.setUsername("%王%");
List<User> list = userDao.findUserByVo(vo);
System.out.println(list);
}
2.2 resultMap标签的使用
- 上面我们说了:实体类属性必须和表的字段名保持一致,这也是为了方便我们的使用,那么实体类和表的字段名不一致,又该如何处理?
1、表:
2、实体类
public class User implements Serializable {
private Integer userid;
private String name;
private Date birth;
private String usersex;
private String useraddress;
/*
* get、set、toString方法
*/
}
我们有两中解决方案:
- 第一种:直接在映射文件中的 SQl 语句 给表字段起别名方式,让实体类属性和表字段名保持一致
栗子:查询所有user
<!--配置查询所有 id:dao里面要执行的方法名-->
<select id="findAll" resultMap="usermap">
select id as userid,username as name,birthday as birth,sex as usersex,address as useraddress from user
</select>
- 第二种: 使用resultMap配置
标签说明:id 是一个唯一标志、type:查询的对应的实体类的全限定名、主键字段对应和非主键字段对应:property=“xxx” column=“xxx” 前面填写的就是我们实体类的属性,后面填写此属性对应的表的哪一列(字段)
<!--配置 查询结果的列名和实体类属性名对应关系-->
<resultMap id="usermap" type="com.My.domain.User">
<!--主键字段对应-->
<id property="userid" column="id"></id>
<!--非主键字段的对应-->
<result property="name" column="username"></result>
<result property="birth" column="birthday"></result>
<result property="usersex" column="sex"></result>
<result property="useraddress" column="address"></result>
</resultMap>
我们还需要一步:把resultMap配置应用到我们的配置映射中,这里还是以:查询所以方法为栗
- 分析上述两个方法的优缺点:
1、直接在映射文件中的 SQl 语句 给表字段起别名方式:这个方式在我们执行到 Sql语句时候就可以直接读取别名,然后去对应在 实体类User 中查找对应属性,执行效率快;但是我们在 Dao 接口每增加一个方法,就要在映射配置中对Sql语句字段起别名,不利于开发!
2、使用resultMap配置:不用我们在执行Sql语句时候一个个去起别名,开发效率高,但是我们要多解析一段html文件(resultMap配置)
3、综上: 追求执行效率选择第一种方式、追求开发效率选择第二种方式!
3、mybatis的dao的编写
- 我们上面的操作都是基于 mybatis 代理dao方式来执行的,那么现在我们不使用代理dao,而自己写实现类,如何开发呢?
- 工程结构:和上面搭建过程是一样的,现在就是我们自己写个实现类:UserDaoImpl 去实现 IUserDao 接口
实现: - IUserDao
/**
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 查询所有操作
*/
List<User> findAll();
/**
* 增
*/
void saveUser(User user);
/**
* 改
*/
void updateUser(User user);
/**
* 删
*/
void deleteUser(Integer id);
/**
* 查
*/
User findById(Integer id);
/**
* 根据名称模糊查询
*/
List<User> findByName(String name);
/**
* 获取用户总记录条数
*/
int findTotal();
}
- UserDaoImpl:
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
public List<User> findAll() {
//1、根据factory 获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2、根据sqlsession 方法实现查询列表
List<User> list = sqlSession.selectList("com.My.dao.IUserDao.findAll");//参数就是能获取到配置信息的key
//释放资源
sqlSession.close();
return list;
}
public void saveUser(User user) {
//1、根据factory 获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2、根据sqlsession 方法实现查询列表
sqlSession.selectList("com.My.dao.IUserDao.saveUser",user);//参数就是能获取到配置信息的key
//3、提交事务
sqlSession.commit();
//4、释放资源
sqlSession.close();
}
public void updateUser(User user) {
//1、根据factory 获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2、根据sqlsession 方法实现查询列表
sqlSession.selectList("com.My.dao.IUserDao.updateUser",user);//参数就是能获取到配置信息的key
//3、提交事务
sqlSession.commit();
//4、释放资源
sqlSession.close();
}
public void deleteUser(Integer id) {
//1、根据factory 获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2、根据sqlsession 方法实现查询列表
sqlSession.selectList("com.My.dao.IUserDao.deleteUser",id);//参数就是能获取到配置信息的key
//3、提交事务
sqlSession.commit();
//4、释放资源
sqlSession.close();
}
public User findById(Integer id) {
return null;
}
public List<User> findByName(String name) {
return null;
}
public int findTotal() {
return 0;
}
}
- 测试:
public class MybatisTest {
private InputStream in;
private IUserDao userDao;
@Before
public void init() throws Exception{
//1、读取配置文件
in = Resources.getResourceAsStream("SqlMapconfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3、使用工厂对象创建 dao 对象
userDao = new UserDaoImpl(factory);
}
@After
public void destroy() throws Exception{
//释放连接
in.close();
}
@Test
public void testfindAll(){
//1、执行查询
List<User> list = userDao.findAll();
System.out.println(list);
}
}
4、mybatis配置的细节(几个标签的使用)
4.1 properties标签的使用及细节
- 引用外部配置文件
我们可不可以像 JDBC 那样,把这读取数据库这几个信息也封装到一个配置文件里面
mybatis是支持我们这样做的,通过properties标签来读取外部的配置文件 jdbcConfig.properties:
对properties标签中属性解释:
1、resources属性: 常用的 用于配置指定文件的位置,是按照类路径的写法来写,并且必须存在于类路径下
2、url属性:是按照 Url 写法来写地址 URL:统一资源定位符,它是可以唯一标识一个资源的位置;写法: 例如:http://localhost:8080/xxx/xxx 就是: 协议 主机 端口 uri ;uri :统一资源标识符,在应用中唯一定位一个资源的
补充: 当我们配置文件 jdbcConfig.properties 没有配置在我们的项目中,而是存在我们电脑的磁盘的文件夹中,那我们就要用 url属性来读取,那有人可能就会说:那我们现在没有,协议、端口号、主机,不满足 url要求,难道我要启动一个 tomcat 去读取配置文件吗?
回答: 难道你就知道 http 协议嘛?我们 windows 系统也支持一种协议:file 协议,那url路径怎么找呢?
做法:找到你的配置文件,把它拖进浏览器里面:
然后你就会看到网址栏有一个网址,这是基于file协议的,默认端口的网址:
配置文件:
<properties url="file:///E:/BaiduNetdiskDownload/01-MyBatis/02-%E7%AC%AC%E4%BA%8C%E5%A4%A9/%E8%B5%84%E6%96%99/jdbcConfig.properties">
</properties>
4.2 typeAliases和package标签
- 1、使用typeAliases可以配置别名,它只能配置我们domain包下的实体类别名
<typeAliases>
<!-- 1、typeAlias 用于配置别名 type:实体类全限定名 alias:别名(别名不区分大小写)-->
<typeAlias type="com.My.domain.User" alias="user"></typeAlias>
</typeAliases>
- 2、package用于指定要配置别名的包,该包下的实体类都会注册别名 ,并且类名就是别名,不区分大小写
用法1:
<typeAliases>
<!--2、用于指定要配置别名的包 该包下的实体类都会注册别名 ,并且类名就是别名,不区分大小写-->
<package name="com.My.domain"></package>
</typeAliases>
用法2:
<!--指定映射配置文件的位置,配置映射文件的是每个 dao 独立的配置文件-->
<mappers>
<!--<mapper resource="mapper/IUserMapper.xml"></mapper>-->
<package name="com.My.dao"/>
</mappers>
三、mybatis的深入和多表
1、mybatis的连接池
- 1、先抛开mybatis框架,现在就说连接池,大家并不陌生,数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
使用: 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的.无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量.连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中.
下面我们来学习 mybatis的连接池,mybatis给我们提供了三种方式的配置,配置的位置就在我们项目中:主配置文件 SqlMapconfig.xml 中的标签里面:
type属性的取值:
- 1、POOLED:采用传统的 javax.sql.DataSource 规范中的连接池,mybatis中有针对规范的实现
- 2、UNPOOLED:采用传统的 javax.sql.DataSource 规范中的连接池,虽然也实现了 java.sql.DataSource 接口,但并未采用"池" 的思想(每次用都重新获取一个连接)
- 3、JNDI:采用服务器提供的 JNDI 技术实现,来获取 DataSource 对象,不同服务器拿到的 DataSource 是不一样的,注意:如果不是 web或者maven的war工程是不能使用的
下面我们通过两种方式来看看POOLED和UNPOOLED连接池的区别:
- 1、运行程序,看看结果
- 2、源码分析
Mybatis 内部分别定义实现了 javax.sql.DataSource 接口中的 UnpooledDataSource,pooledDataSource类来表示 UNPOOLED和POOLED类型的数据源
说明: 这里更重要的是教会大家学习方法,如何去看源码
UnpooledDataSource源码分析:
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
private String driver;
private String url;
private String username;
private String password;
private Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
/**
*此处省略部分源码
*/
public Connection getConnection() throws SQLException {
return this.doGetConnection(this.username, this.password);
}
}
- 我们看到 UnpooledDataSource 实现了 DataSource 接口,这个DataSource 接口就是 javax.sql.DataSource,也就是我们 JDBC 规范中的连接池定义,那么这个连接池定义里面一定会有一个方法:getConnection() 方法
- 所以我们在UnpooledDataSource类中跟踪 getConnection() 方法,发现getConnection() 调用了doGetConnection方法,继续跟踪
doGetConnection方法里面这样做的:创建一个 Properties对象,把用户名和密码设置进去了,又调用了一个 doGetConnection(props),把创建的 Properties放了进去
doGetConnection(props)方法:
再来看看initializeDriver()方法:反射加载驱动
上面的方法不就是我们 JDBC 获取数据库连接步骤嘛:反射加载驱动,然后 DriverManager.getConnection 获取数据库连接
pooledDataSource源码分析:
- 步骤和上面UnpooledDataSource分析一样,下面直接写最终源码:
2、mybatis 中的动态sql
1、if标签
- 我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
实现:
- 1、Dao 接口
/**
* 根据用户信息,查询用户列表
* @param user
* @return
*/
List<User> findByUser(User user);
- 2、Dao 映射配置
注意:标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if> <if test="address != null">
and address like #{address}
</if>
</select>
- 3、测试
@Test
public void testFindByUser() {
User u = new User();
u.setUsername("%王%");
u.setAddress("%顺义%");
//6.执行操作
List<User> users = userDao.findByUser(u);
for(User user : users) {
System.out.println(user);
} }
2、where标签
- 为了简化上面 where 1=1 的条件拼装,我们可以采用标签来简化开发
实现:
- 1、Dao 映射配置
<!-- 根据用户信息查询 --> <select id="findByUser" resultType="user" parameterType="user"> <include refid="defaultSql"></include> <where> <if test="username!=null and username != '' ">
and username like #{username}
</if> <if test="address != null">
and address like #{address}
</if>
</where>
</select>
3、foreach标签
- 1、需求:
传入多个 id 查询用户信息,用下边两个 sql 实现:
SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。
实现:
- 1、在 QueryVo 中加入一个 List 集合用于封装参数
import java.util.List;
public class QueryVo {
private List<Integer> list;
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
}
- 2、Dao 接口
/**
* 根据 id 集合查询用户
* @param vo
* @return
*/
List<User> findInIds(QueryVo vo);
- 3、Dao 映射配置
<!-- 查询所有用户在 id 的集合之中 --> <select id="findInIds" resultType="user" parameterType="queryvo">
<!-- select * from user where id in (1,2,3,4,5); --> <include refid="defaultSql"></include> <where> <if test="ids != null and ids.size() > 0"> <foreach collection="ids" open="id in ( " close=")" item="uid"
separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
说明:
SQL 语句:
select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
- 4、测试
@Test
public void testFindInIds() {
QueryVo vo = new QueryVo();
List<Integer> ids = new ArrayList<Integer>();
ids.add(41);
ids.add(42);
ids.add(43);
ids.add(46);
ids.add(57);
vo.setIds(ids);
//6.执行操作
List<User> users = userDao.findInIds(vo);
for(User user : users) {
System.out.println(user);
} }
3、mybatis的多表查询(一对多,多对一,多对多)
- 我们用的栗子:用户和账户的模型来分析 Mybatis 多表关系。用户为 User 表,账户为Account表。一个用户(User)可以有多个账户(Account)。具体关系如下:
3.1 一对一
- 要求: 查询所有账户信息,关联查询下单用户信息
- 分析: 因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询
- 实现: 现在我们想查询账户信息时候,把关联的用户信息也查询出来,那么主表就是:account,从表就是:user,那我们就可以在创建 实体类 account 时候,把 user当作一个属性也写进去
1、 account 账户实体类
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
/*
* get,set,toString方法
*/
}
2、 IAccountDao接口
public interface IAccountDao {
/**
* 查询所有账户,还要获取当前账户的用户信息
* @return
*/
List<Account> findAll();
}
3、 配置映射及sql
4、测试
//查询所有
@Test
public void testAccount(){
List<Account> list = accountDao.findAll();
for (Account account:list){
System.out.println(account);
System.out.println(account.getUser());
}
}
3.2 一对多
上面我们是:账户对用户,当然一个账户最多有且只有一个用户与之对应,也就是一对一的关系;下面我们用:用户对账户,及就是一个用户下可以有多个账户
- 需求:查询所有用户信息及用户关联的账户信息
- 分析:用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适
- 实现:和上面一样,不过这次我们的主表是:User,从表是:Account,且是一对多,所以
主表实体类应该包含从表实体类的集合引用
1、User实体类
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//一对多关系映射:主表实体类应该包含从表实体类的集合引用
private List<Account> accounts;
/*
*get、set、toString方法
*/
}
2、IUserDao接口
/**
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 查询所有用户,同时获取用户下所有账户信息
*/
List<User> findAll();
}
3、映射配置及sql
4、测试
//查询所有
@Test
public void testUser(){
List<User> list = iUserDao.findAll();
for (User user:list){
System.out.println(user);
System.out.println(user.getAccounts());
}
}
3.3 多对多
- 我们用:用户和角色来演示,一个用户可以有多个角色,反过来,一个角色也可以用不同用户来扮演,为此我们还需要一张中间表
- 分析:建立用户表、角色表。需要用到中间表,中间表包含各自的主键,所以我们要在在中间表建立外键,关系如下:
用户表和角色表:
中间表及创建方式:
- 1、需求:实现查询所有对象并且加载它所分配的用户信息
2、分析:查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息
3、实现
创建 角色 实体类:
public class Role implements Serializable {
private Integer roleId;
private String roleName;
private String roleDesc;
//多对多的关系映射:一个角色可以赋予多个用户
private List<User> users;
/*
*get、set、toString方法
*/
}
编写 Role 持久层接口:
public interface IRoleDao {
/**
* 查询所有角色
* @return
*/
List<Role> findAll();
}
编写映射文件:
<?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">
<!--dao的全限定类名-->
<mapper namespace="com.My.dao.IRoleDao">
<!--定义role表的ResultMap-->
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="users" ofType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="roleMap">
select u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id = ur.rid left outer join user u on u.id = ur.uid;
</select>
</mapper>
测试:
@Test
public void testFindAll(){
List<Role> list = iRoleDao.findAll();
for (Role role:list){
System.out.println(role);
System.out.println(role.getUsers());
}
}
四、mybatis的缓存和注解开发
1、mybatis的延迟加载(加载时机)
- 延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
1.1 使用 assocation 实现延迟加载
- 需求:查询账户信息同时查询用户信息(一对一)
- 账户的持久层 DAO 接口:
public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<Account> findAll();
}
- 账户的持久层映射文件
<?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.itheima.dao.IAccountDao">
<!-- 建立对应关系 --> <resultMap type="account" id="accountMap"> <id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user"
select="com.itheima.dao.IUserDao.findById"
column="uid">
</association>
</resultMap> <select id="findAll" resultMap="accountMap">
select * from account
</select>
</mapper>
select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
- 用户的持久层接口和映射文件
public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}
<?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.itheima.dao.IUserDao">
<!-- 根据 id 查询 --> <select id="findById" resultType="user" parameterType="int" >
select * from user where id = #{uid}
</select>
</mapper>
- 开启 Mybatis 的延迟加载策略:
<settings> <setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
1.2 使用 Collection 实现延迟加载
- 同样我们也可以在一对多关系配置的结点中配置延迟加载策略。< collection>结点中也有 select 属性,column 属性。
- 需求:
完成加载用户对象时,查询该用户所拥有的账户信息 - 1、在 User 实体类中加入 List属性:
- 2、编写用户和账户持久层接口的方法:
/**
* 查询所有用户,同时获取出每个用户下的所有账户信息
* @return
*/
List<User> findAll();
/**
* 根据用户 id 查询账户信息
* @param uid
* @return
*/
List<Account> findByUid(Integer uid);
- 3、编写用户持久层映射配置
<resultMap type="user" id="userMap"> <id column="id" property="id"></id> <result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
--> <collection property="accounts" ofType="account"
select="com.itheima.dao.IAccountDao.findByUid"
column="id">
</collection>
</resultMap>
<!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap">
select * from user
</select>
<collection>标签:
主要用于加载关联的集合对象
select 属性:
用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一
个字段名了
- 4、编写账户持久层映射配置
<!-- 根据用户 id 查询账户信息 --> <select id="findByUid" resultType="account" parameterType="int">
select * from account where uid = #{uid}
</select>
2、mybatis中的一级和二级缓存
- 一级缓存:一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等,方法时,就会清空一级缓存。
- 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
- 二级缓存的开启与关闭
第一步:在 SqlMapConfig.xml 文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
第二步:配置相关的 Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
第三步:配置 statement 上面的 useCache 属性
<!-- 根据 id 查询 --> <select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select> 将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
3、mybatis的注解开发(单表和多表)
- Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。
- mybatis 的常用注解说明:
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
3.1 使用 Mybatis 注解实现基本 CRUD
- 对于未使用注解时候,我们都是通过xxxMapper.xml映射形式来配置,书写不方便,于是出现了一种更为方便的:注解开发;我们可以直接在我们dao接口的方法上进行注解配置,不用再写Mapper.xml文件
- 下面我就直接写针对User 表的CRUD的注解实现代码:
/**
* 在mybatis中针对CRUD一共有四个注解:
* @Select @Insert @Update @Delete
*/
public interface IUserDao{
/**
* 查询所有用户
* @return
*/
@Select("select * from user")//注解如果只有一个value属性,则value可以省略 @Select(value = "xxx")
List<User> findAll();
/**
* 插入
* @param user
*/
@Insert("insert into user(username,address,sex,birthday)" +
"values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
/**
* 改
* @param user
*/
@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday} where id=#{id}")
void updateUser(User user);
/**
* 删
* @param userid
*/
@Delete("delete from user where id=#{id}")
void deleteUser(Integer userid);
/**
* 查
* @param userid
* @return
*/
@Select("select * from user where id=#{id}")
User findById(Integer userid);
/**
* 根据用户名模糊查询
* @param name
* @return
*/
//@Select("select * from user where username like #{username}")
@Select("select * from user where username like '%${value}%'")
List<User> findLikeByname(String name);
}
3.2 mybatis注解建立实体类和表属性对应
- 问:当实体类属性和数据库表字段不一致,通过注解怎么解决?
- 回答:我们有两种解决办法
1、就是在我们注解中的sql语句中起别名(这种方式不采纳,但也是一种选择)
2、使用:@Results注解,下面我们从源码看看怎么使用:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Results {
String id() default "";
Result[] value() default {};
}
上面我们看到@Results有两个属性
- id属性:这个就和我们上面学到的resultMap标签中id作用是一样的,起唯一标识
- Result[] value() default {} :
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Result {
boolean id() default false;
String column() default "";
String property() default "";
Class<?> javaType() default void.class;
JdbcType jdbcType() default JdbcType.UNDEFINED;
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
One one() default @One;
Many many() default @Many;
}
我们可以用到是前三个属性:
boolean id() default false;
String column() default "";
String property() default "";
这不就和我们resultMap中的标签对应起来了嘛:
- id:对应数据库表的唯一字段,主键
- property:实体类的属性名
- column:数据库表列名,字段
- 综上,@Results注解注解的使用:
@Results(id = "",value = {
@Result(id = true,column = "",property = ""),
@Result(column = "",property = ""),
@Result(column = "",property = ""),
@Result(column = "",property = ""),
} )
3.3 mybatis注解开发一对一和多对一查询配置
下面只演示一对一的案例,因为多对一也是照猫画虎,稍作修改,下面我会进行说明
- 我们使用的还是上面用到的:user用户表 和 account账户表
- 案例:查询账户信息的同时把对应的用户信息也查询出来
说明: 可能有人认为这是多对一的关系,因为可能多个账户对应一个用户,但我们查询的原理是:每查询出一个账户,然后根据此账户和用户表的关联字段来查询出对应的用户信息,所以是一对多的关系 - 实现:
1、account实体类:
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//多对一关系映射:从表方应该包含一个主表方的对象引用
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
/**
*这里省略get、set、toString方法
*/
}
2、添加账户的持久层接口并使用注解配置
public interface IAccountDao {
/**
* 查询所有账户,采用延迟加载的方式查询账户的所属用户
* @return
*/
@Select("select * from account")
@Results(id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",
property="user",
one=@One(select="com.itheima.dao.IUserDao.findById",
fetchType=FetchType.LAZY) )
})
List<Account> findAll();
- 对上面注解说明:
3.4 注解开发使用二级缓存
- 1、全局配置开启二级缓存
打开mybatis中文网配置,找到:
- 2、注解配置:
配置在我们的dao接口上
@CacheNamespace(blocking = true)
- mybatis写到这里就结束了,是结束,亦是开始,花开花落一轮回,博主祝大家春招可以找到好的工作,长风破浪会有时,直挂云帆济沧海!