一.mybatis的配置信息
1.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/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件-->
<mappers>
<mapper resource="com/itheima/dao/UserDao.xml"/>
</mappers>
</configuration>
2.Mybatis的映射配置文件
<mapper namespace="com.itheima.Dao.UserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
- 可以看出,一个主配置文件可以配多个映射配置文件,在每个映射配置文件中,通过全限类名和id名可以唯一的确定这个映射配置文件。
- 每个映射配置文件中,有返回类型的全限类名,和sql语句。在以上配置中,我们不需要编写Dao的实现类,就可以执行sql语句,得到返回结果。这是因为mybatis内部使用动态代理和反射的方法来实现的。
二.mybatis的执行过程
public static void main(String[] args) throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlsessionFactory工厂
//可以看到SqlSession是一个接口,必然有一个实现类实现该接口
SqlSessionFactory factory = null;
//sqlsessionFactory构建者生成SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(inputStream);
//3.使用工厂生产sqlsession SqlSession是一个接口,通过工厂模式返回一个实例
SqlSession sqlSession = factory.openSession();
//4.使用sqlsession创建dao接口的代理对象
//通过该对象的getMapper方法,
UserDao userDao = sqlSession.getMapper(UserDao.class);
//5.使用代理对象执行方法
List<User> userList = userDao.findAll();
for(User u:userList){
System.out.println("u = " + u);
}public static void main(String[] args) throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlsessionFactory工厂
//可以看到SqlSession是一个接口,必然有一个实现类实现该接口
SqlSessionFactory factory = null;
//sqlsessionFactory构建者生成SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(inputStream);
//3.使用工厂生产sqlsession SqlSession是一个接口,通过工厂模式返回一个实例
SqlSession sqlSession = factory.openSession();
//4.使用sqlsession创建dao接口的代理对象
//通过该对象的getMapper方法,
UserDao userDao = sqlSession.getMapper(UserDao.class);
//5.使用代理对象执行方法
List<User> userList = userDao.findAll();
for(User u:userList){
System.out.println("u = " + u);
}
//6.释放资源
sqlSession.close();
}
- 阅读源码知道,SqlSessionFactory是接口,SqlSessionFactoryBuilder是一个类,SqlSession是一个接口。接口类型的引用类型说明在内部就创建好了实例对象进行返回,根据编译看左边,运行看右边的机制,这其实是一种多态的使用,只是我们在外部看不出来调用的细节。
- 通过配置文件加载配置信息
- 通过建造者模式创建SqlSessionFactory实例对象
- 通过SqlSessionFactory实例对象的OpenSession方法来创建SqlSession的实例对象
- 通过SqlSession.Mapper方法进行动态代理Dao层接口的方法,实现方法的加强。
三.实现自定义mybatis框架
- 1.工具类
- 解析器类,解析xml文件
- Excutor类,执行sql语句
-DataSourceUtil类,获取连接对象
- 2.配置文件信息类
- 配置信息有,数据库的连接信息,数据库的映射信息。
- 数据库的连接信息,数据库加载的四大要素 username,password,数据库驱动,url地址
- 数据库的映射信息,
<mappers>中对应的<mapper>信息。<mapper></mapper>
中的信息可以通过全限域名和id进行唯一区分mapper。 - Config类,其中有数据库加载的四大要素信息,和mappers<String,mapper>
- Mapper类,属性有执行的sql语句,和要封装的返回类型的全限名称
- 3.SqlSessionFactory接口
SqlSessionFactory 实现类 - 4 SqlSessionBuilder类
- 5 SqlSession接口
SqlSession 实现类 - 6 代理类(mybatis的代理是基于接口的动态代理)
源码如下,亲测成功运行,包结构
public class Configuration {
private Map<String, Mapper> mappers = new HashMap<String, Mapper>();
public String getDriver() {
return Driver;
}
public void setDriver(String driver) {
Driver = driver;
}
private String Driver;
@Override
public String toString() {
return "Configuration{" +
"Driver='" + Driver + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", url='" + url + '\'' +
", mappers=" + mappers +
'}';
}
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Map<String, Mapper> getMappers() {
return mappers;
}
public void setMappers(Map<String, Mapper> mappers) {
//
this.mappers.putAll(mappers);
}
private String password;
private String url;
}
public class Mapper {
/**
* QueryString sql语句
* ResultType 返回类型
* @return
*/
public String getQueryString() {
return queryString;
}
public void setQueryString(String quertString) {
this.queryString = quertString;
}
public String getResultType() {
return ResultType;
}
public void setResultType(String resultType) {
ResultType = resultType;
}
private String queryString;
private String ResultType;
}
public class SqlSessionFactoryBuilder {
public static SqlSessionFactory build(InputStream config){
//加载配置信息
Configuration configuration = XMLConfigBuilder.loadConfiguration(config);
//
System.out.println("configuration = " + configuration);
return new DefaultSqlSessionFactory(configuration);
}
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
//得到配置信息
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
//返回sqlSession实例对象
public SqlSession openSession() {
System.out.println("Facoryconfiguration = " + configuration);
return new DefaultSqlSession(configuration);
}
}
public interface SqlSessionFactory {
SqlSession openSession();
}
public class Resources {
/**
* 获取配置信息资源
*/
public static InputStream getResourceAsStream(String filepath){
return Resources.class.getClassLoader().getResourceAsStream(filepath);
}
}
public class Daoproxy implements InvocationHandler {
private Map<String,Mapper> mappers;
private Connection connection;
public Daoproxy(Map<String, Mapper> mappers, Connection connection) {
this.mappers = mappers;
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//因为mybatis规定配置的id必须和方法名称一致
String methodname = method.getName();
//mappers的key是全域class+method
String classname = method.getDeclaringClass().getName();
System.out.println("classname = " + classname);
System.out.println("methodname = " + methodname);
System.out.println("connection = " + connection);
//拼接key
String key = classname+"."+methodname;
Mapper mapper = mappers.get(key);
return new Executor().selectList(mapper,connection);
}
}
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Connection connection;
public Connection getConnection(){
Connection connection = DataSourceutils.getConnection(configuration);
this.connection = connection;
return connection;
}
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
public <T> T getMapper(Class<T> daoInterfaceClass) {
//进行代理 代理类Daoproxy
//怎么进行代理?
//在mappers中找到对应要执行的方法
//怎么找?
//当前类的名称,调用的方法名称
//调用返回的T的对象的方法,实际上是在调用DaoProxy的invoke方法
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},new Daoproxy(configuration.getMappers(),this.getConnection()));
}
public void close() {
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
}
}
}
}
public interface SqlSession {
public <T> T getMapper(Class<T> daoInterfaceClass);
void close();
public Connection getConnection();
}
public class DataSourceutils {
public static Connection getConnection(Configuration cfg){
try {
Class.forName(cfg.getDriver());
return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}
}
public class Executor {
public <E> List<E> selectList(Mapper mapper, Connection conn) {
PreparedStatement pstm = null;
ResultSet rs = null;
try {
//1.取出mapper中的数据
String queryString = mapper.getQueryString();//select * from user
String resultType = mapper.getResultType();//com.itheima.domain.User
Class domainClass = Class.forName(resultType);
//2.获取PreparedStatement对象
pstm = conn.prepareStatement(queryString);
//3.执行SQL语句,获取结果集
rs = pstm.executeQuery();
//4.封装结果集
List<E> list = new ArrayList<E>();//定义返回值
while(rs.next()) {
//实例化要封装的实体类对象
E obj = (E)domainClass.newInstance();
//取出结果集的元信息:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
//取出总列数
int columnCount = rsmd.getColumnCount();
//遍历总列数
for (int i = 1; i <= columnCount; i++) {
//获取每列的名称,列名的序号是从1开始的
String columnName = rsmd.getColumnName(i);
//根据得到列名,获取每列的值
Object columnValue = rs.getObject(columnName);
//给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
//获取它的写入方法
Method writeMethod = pd.getWriteMethod();
//把获取的列的值,给对象赋值
writeMethod.invoke(obj,columnValue);
}
//把赋好值的对象加入到集合中
list.add(obj);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
release(pstm,rs);
}
}
private void release(PreparedStatement pstm,ResultSet rs){
if(rs != null){
try {
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(pstm != null){
try {
pstm.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
调用流程图
源码会发到github上