自定义 Mybatis 框架

Posted 錵開や落幕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义 Mybatis 框架相关的知识,希望对你有一定的参考价值。

分析流程

1、 引入dom4j

<dependencies>
        <!--<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

2、引入解析xml的工具类

3、引入executor类 负责执行sql语句并且封装结果集


/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 * 负责执行SQL语句,并且封装结果集 返回list集合
 */
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();
            }
        }
    }
}

4、获取数据库连接

public class DataSourceUtil {
    private static Connection conn;
    public static Connection getConnection(Configuration cfg) throws ClassNotFoundException {
        Class.forName(cfg.getDriver());
        try {
            conn=DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

5、编写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">-->
<configuration >
    <!-- 配置环境-->
    <environments default="mysql">
        <!-- 配置mysql环境 -->
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 (连接池) -->
            <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="com/itheim/dao/IUserDao.xml"/>
        <!--<mapper class="com.itheim.dao.IUserDao"/>-->
    </mappers>

</configuration>

**注意**:
此处我们直接使用的是 mybatis 的配置文件,但是由于我们没有使用 mybatis 的 jar 包,所以要把配
置文件的约束删掉否则会报错(如果电脑能接入互联网,不删也行)

6、编写读取配置文件类

public class Resources {
    /**
     * 用于加载 xml 文件,并且得到一个流对象
     * @param xmlPath
     * @return
     * 在实际开发中读取配置文件:
     * 第一:使用类加载器。但是有要求:a 文件不能过大。 b 文件必须在类路径下(classpath)
     * 第二:使用 ServletContext 的 getRealPath()
     */
    public static InputStream getResourceAsStream(String xmlPath){
        return Resources.class.getClassLoader().getResourceAsStream(xmlPath);

    }
}

7、编写mapper类

/**
 *
 * <p>Title: Mapper</p>
 * <p>Description: 用于封装查询时的必要信息:要执行的 SQL 语句和实体类的全限定类名</p>
 * <p>Company: http://www.itheima.com/ </p>
 */
public class Mapper {
    private String QueryString;//查询语句
    private String ResultType;//结果类型

    public String getQueryString() {
        return QueryString;
    }

    public void setQueryString(String queryString) {
        QueryString = queryString;
    }

    public String getResultType() {
        return ResultType;
    }

    public void setResultType(String resultType) {
        ResultType = resultType;
    }

    @Override
    public String toString() {
        return "Mapper{" +
                "QueryString=\'" + QueryString + \'\\\'\' +
                ", ResultType=\'" + ResultType + \'\\\'\' +
                \'}\';
    }
}

8、编写 Configuration 配置类

/**
* 核心配置类
* 1.数据库信息
* 2.sql 的 map 集合
*/
public class Configuration {
    private String Driver;
    private String Url;
    private String Username;
    private String Password;
    private Map<String, Mapper> mappers=new HashMap<String,Mapper>();

    @Override
    public String toString() {
        return "Configuration{" +
                "Driver=\'" + Driver + \'\\\'\' +
                ", Url=\'" + Url + \'\\\'\' +
                ", Username=\'" + Username + \'\\\'\' +
                ", Password=\'" + Password + \'\\\'\' +
                ", Mappers=" + mappers +
                \'}\';
    }

    public String getDriver() {
        return Driver;
    }

    public void setDriver(String driver) {
        Driver = driver;
    }

    public String getUrl() {
        return Url;
    }

    public void setUrl(String url) {
        Url = url;
    }

    public String getUsername() {
        return Username;
    }

    public void setUsername(String username) {
        Username = username;
    }

    public String getPassword() {
        return Password;
    }

    public void setPassword(String password) {
        Password = password;
    }

    public Map<String, Mapper> getMappers() {
        return mappers;
    }

    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers.putAll(mappers);
    }
}

9、编写 User 实体类

/**
 * 封装信息
 */
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username=\'" + username + \'\\\'\' +
                ", birthday=" + birthday +
                ", sex=\'" + sex + \'\\\'\' +
                ", address=\'" + address + \'\\\'\' +
                \'}\';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

基于 XML 的自定义 mybatis 框架

1、编写持久层接口和 IUserDao.xml

public interface IUserDao {
    /**
* 查询所有用户
* @return
*/
    public List<User> 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.itheim.dao.IUserDao">
    <select id="findAll" resultType="com.itheim.domain.User"> <!--指定包装类型 -->
        select * from user;
    </select>
</mapper>
**注意**:
此处我们使用的也是 mybatis 的配置文件,所以也要把约束删除了

2、编写构建者类

/**
 *
 * <p>Title: SqlSessionFactoryBuilder</p>
 * <p>Description: 用于构建 SqlSessionFactory 的</p>
 * <p>Company: http://www.itheima.com/ </p>
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in){
        Configuration configuration = XMLConfigBuilder.loadConfiguration(in);
        return new DefaultSqlSessionFactory(configuration);
    }
}

3、编写 SqlSessionFactory 接口和实现类

/**
 *
 * <p>Title: SqlSessionFactory</p>
 * <p>Description: SqlSessionFactory 的接口</p>
 * <p>Company: http://www.itheima.com/ </p>
 */
public interface SqlSessionFactory {
    public SqlSession openSession() throws ClassNotFoundException;
}

/**
 *
 * <p>Title: DefaultSqlSessionFactory</p>
 * <p>Description:SqlSessionFactory 的默认实现 </p>
 * <p>Company: http://www.itheima.com/ </p>
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration cfg=null;

    public DefaultSqlSessionFactory(Configuration cfg) {
        this.cfg = cfg;
    }

    @Override
    public SqlSession openSession() throws ClassNotFoundException {
        return new DefaultSqlSession(cfg);
    }
}

4、编写 SqlSession 接口和实现类

public interface SqlSession {
    /**
     * 创建dao接口的代理对象
     * @param daoClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> daoClass);

    /**
     * 关闭资源
     */
    public void close();
}
/**
 *
 * <p>Title: DefaultSqlSession</p>
 * <p>Description: SqlSession 的具体实现</p>
 * <p>Company: http://www.itheima.com/ </p>
 */
public class DefaultSqlSession implements SqlSession {
    private Configuration cfg;
    private Connection conn;

    public DefaultSqlSession(Configuration cfg) throws ClassNotFoundException {
        this.cfg = cfg;
        conn = DataSourceUtil.getConnection(cfg);
    }

    @Override
    public <T> T getMapper(Class<T> daoClass) {
        return (T)Proxy.newProxyInstance(daoClass.getClassLoader(),new Class[]{daoClass},new MapperProxy(cfg.getMappers(),conn));
    }

    @Override
    public void close() {
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}


动态代理

/**
* 动态代理:
* 涉及的类:Proxy
* 使用的方法:newProxyInstance
* 方法的参数:
* ClassLoader:和被代理对象使用相同的类加载器,通常都是固定的
* Class[]:代理对象和被代理对象要求有相同的行为。(具有相同的方法)
* InvocationHandler:如何代理。需要我们自己提供的增强部分的代码
*/
public class MapperProxy implements InvocationHandler {
    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 {
        String methodName = method.getName(); //获取方法名
        String className = method.getDeclaringClass().getName();//获取方法所在类的名
        //组合key
        String key=className+"."+methodName;
        System.out.println(key);
        //4.使用 key 取出 mapper
        Mapper mapper = mappers.get(key);
        Executor executor = new Executor();
        //5.判断是否有mapper
        if(mapper == null){
            throw new IllegalArgumentException("传入的参数有误");
        }
        return executor.selectList(mapper,conn);
    }
}

5、运行测试类

public class UserDao{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //读取配置文件
        InputStream in =  Resources.getResourceAsStream("mybatisConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //使用工厂生产Sqlsession对象
        SqlSession session = factory.openSession();
        //使用Sqlsession创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);
       //实现类的方法
//        IUserDao userDao=new UserDaoImp(factory);
        List<User> users=userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }

        //释放资源
//        session.close();
        in.close();


    }
}

基于注解方式定义 Mybatis 框架

自定义@Select 注解

/** 
* <p>Title: Select</p>
* <p>Description: 自定义查询注解</p>
* <p>Company: http://www.itheima.com/ </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String value();
}

以上是关于自定义 Mybatis 框架的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis进阶之自定义MyBatis框架

如何自定义MyBatis框架

阶段3 1.Mybatis_04.自定义Mybatis框架基于注解开发_2 回顾自定义mybatis的流程分析

自定义 Mybatis 框架

《Java手写系列》-手写MyBatis框架

[mybatis]动态sql_sql_抽取可重用的sql片段