什么是 MyBatis?
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<User>
List<User> users = userMapper.findAll();
for (User user : users) {
System.out.println(user);
}
//7.释放资源
sqlSession.close();
in.close();
}
}
mybatis几个基础API
** 二、API设计与实现**
maven依赖准备
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- 灵活的Java XML框架 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- Jaxen是用于Java的通用XPath引擎 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
1、Resources类
public class Resources {
/**
* 用于加载 .xml文件,获取一个流对象
*
* @param xmlPath
* @return
*/
public static InputStream getResourceAsStream(String xmlPath) {
return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
}
}
2、建造者模式
public interface SqlSessionFactory {
/**
* 创建一个新的 SqlSession 对象
*
* @return
*/
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private InputStream input;
public void setInput(InputStream input) {
this.input = input;
}
/**
* 创建一个新的SqlSession对象
* @return
*/
@Override
public SqlSession openSession() {
DefaultSqlSession sqlSession = new DefaultSqlSession();
//调用 xml解析器解析.xml文件
XMLConfigBuilder.loadConfiguration(sqlSession, input);
return sqlSession;
}
}
建造者类
public class SqlSessionFactoryBuilder {
/**
* 根据传入的流,实现对 SqlSessionFactory 的创建
*
* @param inputStream
* @return
*/
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<String, Mapper> 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 {
/**
* 创建dao接口的代理对象
*
* @param daoClass
* @param <T>
* @return
*/
<T> T getMapper(Class<T> 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();
}
}
/**
* 动态代理
* 生成需要的Mapper
*
* @param daoClass
* @param <T>
* @return
*/
@Override
public <T> T getMapper(Class<T> 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 <E> List<E> 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<String, Mapper> mappers;
public MapperProxyFactory(Connection connection, Map<String, Mapper> mappers) {
this.connection = connection;
this.mappers = mappers;
}
/**
* Map 中获取 Value(Mapper),使用工具类 Executor 的 selectList 方法
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@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 {
/**
* 查询语句执行器,同时解析结果并返回
*
* @param mapper
* @param con
* @param <E>
* @return
*/
public <E> List<E> 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<E> 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);
}
}
/**
* 释放资源
*
* @param ps
* @param rs
*/
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 {
/**
* 解析主配置文件,把里面的内容填充到 DefaultSqlSession 所需要的地方
*
* @param session
* @param input
*/
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<Element> 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<Element> 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<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
cfg.setMapperMap(mappers);
} else {
//System.out.println("使用的是注解");
String daoClassPath = mapperElement.attributeValue("class");
//根据 daoClassPath 获取封装的必要信息,给 configuration 中的 mappers 赋值
Map<String, Mapper> 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();
}
}
}
/**
* 根据 注释传入的参数,解析 XML,并且封装到 Map 中
*
* @param daoClassPath
* @return
*/
private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws ClassNotFoundException {
//定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
//得到 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<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;
}
/**
* 根据 xml文件传入的参数,解析 XML,并且封装到 Map 中
*
* @param mapperPath
* @return
*/
private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) {
InputStream in = null;
try {
//定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
//根据路径获取字节输入流
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<Element> 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 {
/**
* 根据 Configuration 配置类信息创建数据库连接
*
* @param cfg
* @return
*/
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文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<environments default="mysql">
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="{username}"/>
<property name="password" value="{password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.qiu.dao.UserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.qiu.model.User">
select *
from tb_users;
</select>
</mapper>
这里实体类需要自己设设计以及数据库表
** 注意:** 需要留意的是,由于我们在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<User>
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
}
【测试结果】
** 2、测试注解@select**
配置文件同上、测试类同上
注意将SqlMapConfig.xml文件的mapper标签替换属性值为class,启动使用注解
<mappers>
<mapper class="com.qiu.dao.UserDao"/>
</mappers>
** OK!大工告成!!!**
版权归原作者 枫蜜柚子茶 所有, 如有侵权,请联系我们删除。