MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一、mybatis入门案例【测试类代码】
public class MybatisTest {
public static void main(String[] args) throws IOException {
//1、读取配置文件
InputStream in = Resources.getResourceAsStream("config/SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory sessionFactory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession sqlSession = sessionFactory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
UserDao userMapper = sqlSession.getMapper(UserDao.class);
//6.使用代理对象执行查询所有方法 List
List users = userMapper.findAll();
for (User user : users) {
System.out.println(user);
}
//7.释放资源
sqlSession.close();
in.close();
}
}
mybatis几个基础API
二、API设计与实现
maven依赖准备
mysql mysql-connector-java8.0.11 dom4j dom4j1.6.1 jaxen jaxen1.1.6 log4j log4j1.2.12 junit junit4.12 test
1、Resources类
public class Resources {
public static InputStream getResourceAsStream(String xmlPath) {
return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
}
}
2、建造者模式
public interface SqlSessionFactory {
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private InputStream input;
public void setInput(InputStream input) {
this.input = input;
}
@Override
public SqlSession openSession() {
DefaultSqlSession sqlSession = new DefaultSqlSession();
//调用 xml解析器解析.xml文件
XMLConfigBuilder.loadConfiguration(sqlSession, input);
return sqlSession;
}
}
建造者类
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory();
factory.setInput(inputStream);
return factory;
}
}
3、核心配置类Configuration
Configuration类
public class Configuration {
private String username; //用户名
private String password;//密码
private String url;//地址
private String driver;//驱动
private Map mapperMap; //map 集合 Map<唯一标识,Mapper> 用于保存映射文件中的 sql标识及 sql 语句
//set、get方法
}
Mapper类
public class Mapper {
private String queryString;//sql
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;
}
}
SqlSession类 (接口将在下一步实现)
public interface SqlSession {
T getMapper(Class daoClass);
void close();
}
4、动态代理
DefaultSqlSession类
public class DefaultSqlSession implements SqlSession {
private Configuration cfg;
private Connection con;
public void setCfg(Configuration cfg) {
this.cfg = cfg;
}
public Connection getConnection() {
try {
con = DataSourceUtil.getConnection(cfg);
return con;
} catch (Exception e) {
throw new RuntimeException();
}
}
@Override
public T getMapper(Class daoClass) {
Connection con = getConnection();
//动态代理生成对象
T daoProxy = (T) Proxy.newProxyInstance(daoClass.getClassLoader(),
new Class[]{daoClass} ,
new MapperProxyFactory(con, cfg.getMapperMap()));
return daoProxy;
}
@Override
public void close() {
try {
System.out.println(con);
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//查询所有方法
public List selectList(String statement) {
Mapper mapper = cfg.getMapperMap().get(statement);
return new Executor().selectList(mapper, con);
}
}
MapperProxyFactory类
public class MapperProxyFactory implements InvocationHandler {
private Connection connection;
private Map mappers;
public MapperProxyFactory(Connection connection, Map mappers) {
this.connection = connection;
this.mappers = mappers;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
//拼接key
String key = className + "." + methodName;
//,获取对应mapper
Mapper mapper = mappers.get(key);
//如果mapper为空
if (mapper == null) {
throw new IllegalArgumentException(key + "is not exit");
}
Executor executor = new Executor();
//调用executor的selectList获取所有方法
return executor.selectList(mapper, connection);
}
}
5、Executor执行类
public class Executor {
public List selectList(Mapper mapper, Connection con) {
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//读取mapper数据
String queryString = mapper.getQueryString(); //获取sql查询语句
String resultType = mapper.getResultType(); //全限定类名
Class typeClass = Class.forName(resultType);
//获取PrepareStatement对象
statement = con.prepareStatement(queryString);
//执行sql语句
resultSet = statement.executeQuery();
//解析数据
List list = new ArrayList<>();
while (resultSet.next()) {
//通过反射,创建结果实体对象
E obj = (E) typeClass.newInstance();
//获取结果集合的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
int count = metaData.getColumnCount(); //获取数据列数
//循环遍历每列数据(下标从1开始)
for (int i = 1; i <= count; i++) {
//获取指定列的名字
String name = metaData.getColumnName(i);
//根据列名获取对应列的值
Object columnValue = resultSet.getObject(name);
//java内省机制,通过反射获取javabean的setting方法并通过method执行
PropertyDescriptor pd = new PropertyDescriptor(name, typeClass);
Method method = pd.getWriteMethod();
method.invoke(obj, columnValue);
}
//数据添加入list
list.add(obj);
}
//返回解析结果
return list;
} catch (Exception e) {
throw new RuntimeException();
} finally {
//释放资源
release(statement, resultSet);
}
}
private void release(PreparedStatement ps, ResultSet rs) {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
6、xml解析类
public class XMLConfigBuilder {
public static void loadConfiguration(DefaultSqlSession session, InputStream input) {
try {
//定义封装连接信息的配置对象(mybatis的配置对象)
Configuration cfg = new Configuration();
//获取 SAXReader 对象
SAXReader reader = new SAXReader();
//根据字节输入流获取 Document 对象
Document document = reader.read(input);
//获取根节点
Element root = document.getRootElement();
//使用 xpath 中选择指定节点的方式,遍历获取所有 property 节点
List propertyElements = root.selectNodes("//property");
for (Element propertyElement : propertyElements) {
//判断节点是连接数据库的哪部分信息,取出 name 属性的值
String name = propertyElement.attributeValue("name");
if ("driver".equals(name)) {
//表示驱动
String driver = propertyElement.attributeValue("value");
cfg.setDriver(driver);
}
if ("url".equals(name)) {
//表示连接字符串
String url = propertyElement.attributeValue("value");
cfg.setUrl(url);
}
if ("username".equals(name)) {
//表示用户名
String username = propertyElement.attributeValue("value");
cfg.setUsername(username);
}
if ("password".equals(name)) {
//表示密码
String password = propertyElement.attributeValue("value");
cfg.setPassword(password);
}
}
//取出 mappers中的所有 mapper 标签,判断他们使用了 resource 还是 class 属性
List mapperElements = root.selectNodes("//mappers/mapper");
//遍历集合
for (Element mapperElement : mapperElements) {
//判断 mapperElement 使用的是哪个属性
Attribute attribute = mapperElement.attribute("resource");
if (attribute != null) {
//System.out.println("使用的是 XML");
String mapperPath = attribute.getValue();
//把映射配置文件的内容获取出来,封装成一个 map,给 configuration 中的 mappers 赋值
Map mappers = loadMapperConfiguration(mapperPath);
cfg.setMapperMap(mappers);
} else {
//System.out.println("使用的是注解");
String daoClassPath = mapperElement.attributeValue("class");
//根据 daoClassPath 获取封装的必要信息,给 configuration 中的 mappers 赋值
Map mappers = loadMapperAnnotation(daoClassPath);
cfg.setMapperMap(mappers);
}
}
//把配置对象传递给 DefaultSqlSession
session.setCfg(cfg);
} catch (DocumentException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Map loadMapperAnnotation(String daoClassPath) throws ClassNotFoundException {
//定义返回值对象
Map mappers = new HashMap();
//得到 dao 接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//得到 dao 接口中的方法数组
Method[] methods = daoClass.getMethods();
//遍历 Method 数组
for (Method method : methods) {
//取出每一个方法,判断是否有 select 注解
if (method.isAnnotationPresent(Select.class)) {
//创建 Mapper 对象
Mapper mapper = new Mapper();
//取出注解的 value 属性值
Select selectAnno = method.getAnnotation(Select.class);
String queryString = selectAnno.value();
mapper.setQueryString(queryString);
//获取当前方法的返回值,还要求必须带有泛型信息
Type type = method.getGenericReturnType();//List
//判断 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;
}
private static Map loadMapperConfiguration(String mapperPath) {
InputStream in = null;
try {
//定义返回值对象
Map mappers = new HashMap();
//根据路径获取字节输入流
in = Resources.getResourceAsStream(mapperPath);
//根据字节输入流获取 Document 对象
SAXReader reader = new SAXReader();
Document document = reader.read(in);
//获取根节点,获取根节点的 namespace 属性取值
Element root = document.getRootElement();
String namespace = root.attributeValue("namespace");//是组成 map 中 key 的部分
//获取所有的 select 节点
List selectElements = root.selectNodes("//select");
//遍历 select 节点集合
for (Element selectElement : selectElements) {
String id = selectElement.attributeValue("id");
String resultType = selectElement.attributeValue("resultType");
//取出文本内容 组成 map 中的SQL语句
String querySql = selectElement.getText();
//创建 Key
String key = namespace + "." + id;
//创建 Value
Mapper mapper = new Mapper();
mapper.setQueryString(querySql);
mapper.setResultType(resultType);
//把 key 和 value 存入 mappers 中
mappers.put(key, mapper);
}
return mappers;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7、自定义注解类
以实现注解@select为例子
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Select {
String value();
}
8、工具类DataSourceUtil
public class DataSourceUtil {
public static Connection getConnection(Configuration cfg) {
try {
//加载数据库驱动
Class driver = Class.forName(cfg.getDriver());
//获取数据库连接
Connection connection = DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
return connection;
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException();
}
}
}
三、测试样例
1、mapper.xml文件映射
SqlMapConfig.xml文件
UserMapper.xml文件
这里实体类需要自己设设计以及数据库表
注意: 需要留意的是,由于我们在executor类中,利用java内省机制,获取set方法,给对象属性注入赋值,所以实体的属性名一定要与数据库对应列名完全一样,否则会报错!
【测试类】
public class Test {
public static void main(String[] args) throws IOException {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//6.使用代理对象执行查询所有方法 List
List users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
}
【测试结果】
2、测试注解@select
配置文件同上、测试类同上
注意将SqlMapConfig.xml文件的mapper标签替换属性值为class,启动使用注解
OK!大工告成!!!



