一、MyBatis介绍、入门案例以及自定义实现MyBatis
1、什么是框架?
它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。
使用框架的好处:
框架封装了很多的细节,使开发者可以使用极简的方式实现功能。大大提高开发效率。
2、三层架构
- 表现层:是用于展示数据的
- 业务层:是处理业务需求(Spring不属于任何一个层)
- 持久层:是和数据库交互的
3、持久层技术解决方案
JDBC技术:
- Connection
- PreparedStatement
- ResultSet
Spring的JdbcTemplate:Spring中对jdbc的简单封装
Apache的DBUtils:它和Spring的JdbcTemplate很像,也是对Jdbc的简单封装
以上这些都不是框架
JDBC是规范、
Spring的JdbcTemplate和Apache的DBUtils都只是工具类
4、mybatis的概述
mybatis是一个持久层框架,用java编写的。
它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程
它使用了ORM思想实现了结果集的封装。
ORM:
Object Relational Mappging 对象关系映射。简单的说,就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表。
user User
id userId
user_name userName
今天我们需要做到
实体类中的属性和数据库表的字段名称保持一致。
user User
id id
user_name user_name
5、mybatis的入门
mybatis的环境搭建
demo的基本架构
第一步:创建maven工程并导入坐标
第二步:创建实体类和dao的接口
第三步:创建Mybatis的主配置文件 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=""></transactionManager>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql//localhost:3307/mybatis1029"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,指的是每个dao独立的-->
<mappers>
<mapper resource="com.sc.dao.IUserDao"/>
</mappers>
</configuration>
第四步:创建映射配置文件 IUserDao.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">
<!--namespace指的是你的dao接口-->
<mapper namespace="com.sc.dao.IUserDao">
<!--配置查询所有-->
<!--其中id不能乱写必须是dao接口中的方法 resultType写的是实体类的全路径-->
<select id="findAll" resultType="com.sc.dao.IUserDao">
select * from user;
</select>
</mapper>
环境搭建的注意事项:
第一个:创建IUserDao.xml 和 IUserDao.java时名称是为了和我们之前的知识保持一致。
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:IUserDao 和 IUserMapper是一样的
第二个:在idea中创建目录的时候,它和包是不一样的
包在创建时:com.itheima.dao它是三级结构
目录在创建时:com.itheima.dao是一级目录
第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。
mybatis的入门案例
第一步:读取配置文件
第二步:创建SqlSessionFactory工厂
第三步:创建SqlSession
第四步:创建Dao接口的代理对象
第五步:执行dao中的方法
第六步:释放资源
//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> users = userDao.findAll();
for (User user:users) {
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
注意事项:
不要忘记在映射配置中告知mybatis要封装到哪个实体类中
配置的方式:指定实体类的全限定类名
mybatis基于注解的入门案例:
把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句
同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。
明确:
我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。
不管使用XML还是注解配置。
但是Mybatis它是支持写dao实现类的。
测试类
public class MybatisTest {
public static void main(String[] args) throws Exception {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
//3.使用工厂创建SqlSession
SqlSession session = factory.openSession();
//4.使用SqlSession创建dao接口的代理对象
IUserDao userDao =session.getMapper(IUserDao.class);
//5.使用代理对象的方法
List<User> users = userDao.findAll();
for (User user:users
) {
System.out.println(user);
}
//6.释放资源
session.close();
inputStream.close();
}
}
入门案例使用到的设计模式:
自定义Mybatis的分析
如果使用了Dao.Impl:
mybatis在使用代理dao的方式实现增删改查时做什么事呢?
只有两件事:
- 创建代理对象
- 在代理对象中调用selectList
查询所有session.selectList的分析:
session.getMapper(IUserDao.class)是怎么创建代理的呢?
创建代理对象的分析:
代理对象:
- 类加载器:要代理谁,就用谁的类加载器
- 代理对象要实现的接口:和被代理对象实现相同的接口
- 如何代理:增强的方法——invocationHandler接口,在实现类中调用selectList方法
自己定义mybatis:
自定义mybatis能通过入门案例看到类
class Resources:
class SqlSessionFactoryBuilder:
interface SqlSessionFactory:
interface SqlSession:
XMLBuilder工具类:
把mybatis的坐标删了
XML实现:
-
resource类读取配置文件,读成字节流
/** * 使用类加载器读取配置文件的类 */ public class Resources { /** * 根据传入的参数,获取一个字节输入流 * @param filePath * @return */ public static InputStream getResourceAsStream(String filePath){ return Resources.class.getClassLoader().getResourceAsStream(filePath); } }
-
读取出的信息,使用工具类XMLBuilder封装到Configuration参数类中
setMappers方法要特别注意
/** * 自定义mybatis配置类 */ public class Configuration { private String driver; private String url; private String username; private String password; private Map<String,Mapper> mappers = new HashMap<String, Mapper>(); public Map<String, Mapper> getMappers() { return mappers; } //由于还支持别的dao进行mapper映射,setMappers应该是追加而不是覆盖,不然只能配置一个 public void setMappers(Map<String, Mapper> mappers) { //追加而不是覆盖 this.mappers.putAll(mappers); } //各种getset方法 }
交给构建者SqlSessionFactoryBuilder
/** * 用于创建一个SqlSessionFactoryBuilder对象 */ public class SqlSessionFactoryBuilder { /** * 根据参数的字节输入流来构建一个SqlSessionFactory工厂 * @param config * @return */ public SqlSessionFactory build(InputStream config){ Configuration cfg = XMLConfigBuilder.loadConfiguration(config); return new DefaultSqlSessionFactory(cfg); } }
构建者构建了工厂对象——SqlSessionFactory接口的实现类DefaultSqlSessionFactory
/** * SqlSessionFactory接口的实现类 */ public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration cfg; public DefaultSqlSessionFactory(Configuration cfg){ this.cfg = cfg; } /** * 用于创建一个新的操作数据库对象 * @return */ @Override public SqlSession openSession() { return new DefaultSqlSession(cfg); } }
-
工具类DataSourceUtil用于创建Connection
/** * 用于创建数据源的工具类 */ public class DataSourceUtil { /** * 用于获取一个连接 * @param cfg * @return */ public static Connection getConnection(Configuration cfg){ try{ Class.forName(cfg.getDriver()); return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword()); }catch (Exception e){ throw new RuntimeException(e); } } }
Mapper对象封装sql和结果的全限定名
/** * 用于封装sql和结果的全限定类名 */ public class Mapper { private String queryString; private String resultType;//实体类的全限定类名 public String getQueryString() { return queryString; } public void setQueryString(String queryString) { this.queryString = queryString; } public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } }
工厂对象openSession给我们提供了SqlSession接口的实现DefaultSqlSession,在这个里面实现创建代理对象和查询所有的操作。
/** * SqlSession接口的实现 */ public class DefaultSqlSession implements SqlSession { private Configuration cfg; private Connection connection; public DefaultSqlSession(Configuration cfg){ this.cfg = cfg; connection = DataSourceUtil.getConnection(cfg); } /** * 创建代理对象 * @param daoInterfaceClass dao的接口字节码 * @param <T> * @return */ @Override public <T> T getMapper(Class<T> daoInterfaceClass) { //强制转换不要忘了 return (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection)); } /** * 释放资源 */ @Override public void close() { if (connection != null) try { connection.close(); }catch (Exception e){ e.printStackTrace(); } } }
其中MapperProxy即为代理对象,执行Executor工具类的selectList方法进行增强,返回一个List给Session
public class MapperProxy implements InvocationHandler { //map的key是全限定类名+方法名 private Map<String,Mapper> mappers; private Connection conn; public MapperProxy(Map<String,Mapper> mappers,Connection conn){ this.mappers = mappers; this.conn = conn; } /** * 用于对方法进行增强,就是调用selectList方法 * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //1.获取方法名 String methodName = method.getName(); //2.获取类名 String className = method.getDeclaringClass().getName(); //3.组合 String key = className+"."+methodName; //4.获取mappers中的mapper对象 Mapper mapper = mappers.get(key); if (mapper == null){ throw new IllegalArgumentException("传入参数有误"); } //调用 return new Executor().selectList(mapper,conn); } }
最终就是Dao调用getMapper方法,创建实现类增强
IUserDao userDao = session.getMapper(IUserDao.class);
注解方法类似
注解实现:
SqlMapConfig.xml中mapper格式改成class
<mappers>
<!--xml <mapper resource="com/itheima/dao/IUserDao.xml"/>-->
<mapper class="com.sc.dao.IUserDao"/>
</mappers>
Dao接口使用@Select
public interface IUserDao {
/**
* 查询所有操作
* @return
*/
@Select("select * from user")
List<User> findAll();
}
创建注解Select.java
@Retention(RetentionPolicy.RUNTIME)//生命周期
@Target(ElementType.METHOD)//出现位置
public @interface Select {
/**
* 配置SQL语句的
* @return
*/
String value();
}
在工具类中
反射注解填充Map<String,Mapper>
对象mappers
private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
//定义返回值对象
Map<String,Mapper> mappers = new HashMap<String, Mapper>();
//1.得到dao接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//2.得到dao接口中的方法数组
Method[] methods = daoClass.getMethods();
//3.遍历Method数组
for(Method method : methods){
//取出每一个方法,判断是否有select注解
boolean isAnnotated = method.isAnnotationPresent(Select.class);
if(isAnnotated){
//创建Mapper对象
Mapper mapper = new Mapper();
//取出注解的value属性值
Select selectAnno = method.getAnnotation(Select.class);
String queryString = selectAnno.value();
mapper.setQueryString(queryString);
//获取当前方法的返回值,还要求必须带有泛型信息
Type type = method.getGenericReturnType();//List<User>
//判断type是不是参数化的类型
if(type instanceof ParameterizedType){
//强转
ParameterizedType ptype = (ParameterizedType)type;
//得到参数化类型中的 实际类型参数
Type[] types = ptype.getActualTypeArguments();
//取出第一个
Class domainClass = (Class)types[0];
//获取domainClass的类名
String resultType = domainClass.getName();
//给Mapper赋值
mapper.setResultType(resultType);
}
//组装key的信息
//获取方法的名称
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String key = className+"."+methodName;
//给map赋值
mappers.put(key,mapper);
}
}
return mappers;
}