完全从零Java自学系列【入门篇】(第五课:JAR包&JAVA中的第三方依赖&使用MAVEN管理工程的生命周期&数据库连接池&MyBatis框架&反射&模版方法设计模式&动态代理设计模式)

提示

  再次强调,本系列文主要是对JAVA整个过程中的全部主线任务初体验。结合前面的课程中,如果你发现越来越多不理解的点则需要多一些的比较思考和额外的探索。
  大量的手写练习是必然需要的,但是更加重要的是编程思想或思维的形成。代码只是你的工具,先熟悉这个工具。然后真正怎么使用,需要自己非常强的独立思考能力。

JAR包

  目前我们每次执行代码的方式必须要打开IDEA,在实际的场景中,我们不可能在服务器上还去安装一个IDEA。别的不说IDEA本身就暂用很大的内存。
  前面课程我们第一课环境介绍时提到过,实际上IDEA在你点击运行或调试时。后台帮你做了 编译 -> 打包 -> 启动JAVA进程运行你代码 的过程。

JAVA文件类型

  在JAVA中,常见的文件类型有.java,.class,.jar,.war(后面课程会使用到)。让我们来使用IDEA编译打包一下看看它们到底是什么。

java与class

  在IDEA中,项目目录中有out目录,此out目录就是IDEA存放编译结果的地方。编译就是将.java转换为.class文件。java是源代码文件,class是java能识别的文件(所以在编译的过程中,很多时候可能和你写的你认为的执行过程会不同,这也是未来深入研究的方向之一,目前知道即可)。
在这里插入图片描述

  区别演示(左java,右class),目前只需要知道它们是有区别的:

  1. 所有的注释内容都被去除了,一是没有必要注释是给人看的,二是缩小文件大小自
  2. public Grammar() {}构造函数默认是存在的,当你没有写别的重载构造函数时。
  3. 源码使用for循环,class文件却使用了while(true)
  4. int customerInput调整到了int customerInput = scanner.nextInt();
  5. 将break优化为了return。
    在这里插入图片描述
可执行文件jar

  我们将我们的项目打包为jar,这样每次运行只需要java环境即可。不需要IDEA工具了。

IDEA打包前的配置

  1. 通过工具左上角的File选项进入Project Structure,选择Artifacts(神器,有时候编程的世界里命名就是会比较抽象一些),选择JAR并且From modules with dependencies(我们在上一课中引入了Mysql jar)。在这里插入图片描述
  2. 在弹出框中,Module就是项目模块,直接选择。Main Class必须选择拥有main函数的类作为程序主入口,运行jar时需要从你指定的入口执行。在这里插入图片描述
  3. 上一步中的META-INF下的MANIFEST.MF文件内容Manifest-Version: 1.0 Main-Class: Lesson1.Grammar。每个jar包都有这个文件,起到标识主程序入口的功能,java也知道怎么运行你得代码了。
  4. 编译后的目录配置,名字也是可以自己修改的,Available Elements标识了将有哪些外部引入会一同打包。 在这里插入图片描述
  5. 编译并打包在这里插入图片描述
  6. 如果查看jar文件内容则需要安装反编译工具:
    Windows&MAC 安装【工具篇】(JD-GUI反编译工具&mac与windows安装使用)
  7. 在命令行中使用java来执行jar包,命令行进入jar包所在目录。在这里插入图片描述

main函数中的 String[] args

  到目前为止,我们可以解释的通这个参数是做什么用的了,在java -jar Basic-EDU.jar时,我们还可以在后面追加输入参数:java -jar <JAR文件路径> <启动参数1> <启动参数2>。所有的启动参数将被组合为一个String[] args数组。在程序中动态的去处理它们。

MAVEN

  在上一课以及这一课中,我们示范了如何在idea的工程中引入jar并使用。并将所有的依赖进行打包。

  1. 在真实的实战环境时,通常需要非常非常多的jar,每一个jar又有非常多的版本。每一次需要更改版本都要手动的去下载。
  2. 同一个工程下会有相当多的模块区分,模块与模块之间相互依赖。每一次的编译构建都将需要重新引入到工程内。

安装与使用

  MAVEN是一个能够帮助你1、管理依赖库,2、构建代码库,3、发布代码库的命令行工具。所以它和JAVA一样,如果不搭建环境,系统将无法识别java命令。请参考【工具篇】(MAVEN编译工具安装与基础使用&工程管理&依赖管理)

数据库连接池

  但凡是xxx池(线程池,连接池)的,它最初的设计初中都只会是节省创建和销毁这两个最重要周期的时间罢了。(那就不能直接用吗?还申请什么?-> 那就乱套了,所有程序想用什么资源就用什么资源的话)
  讲解之前我们把mysql与alibaba数据库连接池依赖加入到项目中:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.33</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.10</version>
</dependency>

在这里插入图片描述

在JAVA中使用alibaba druid线程池

  首先我们使用比之前的写法更为优美一些的封装写法,来完成这个池的使用。在这个类中完成初始化,关闭连接等操作。为了节省文章的长度,我将所有讲解尽量写入到代码中的注释内容中:
工具类源码

package lesson5;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class MysqlDruid {
    //数据库连接串 ip地址 端口 数据库edu_samuel
    private static final String DB_CONN = "jdbc:mysql://localhost:3306/edu_samuel?useSSL=false";
    /**
     * 用户名密码
     */
    private static final String DB_UN = "root";
    private static final String DB_PW = "123456789";
    private static DruidDataSource druidDataSource;

    static {
        /**
         * 设置三大属性 1.数据库连接串 2.用户名 3.密码
         */
        druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(DB_CONN);
        druidDataSource.setUsername(DB_UN);
        druidDataSource.setPassword(DB_PW);

        //最大活跃连接数,和线程池中的最大线程数一致。为什么可以设置这么大,我们在讲解线程池时已经说过了。
        // 在这里如果sql需要执行的时间可能很长的话可以设置大一些。
        druidDataSource.setMaxActive(20);
        //初始连接数量
        druidDataSource.setInitialSize(5);
        //允许的空闲连接,在没有任务要执行的时候,相当于线程池的核心线程数
        druidDataSource.setMinIdle(5);
        //通过上面的配置,超过5个以外的连接数将在闲置50000毫秒后释放。
        druidDataSource.setMaxWait(50000);
        /**
         * 在实际项目中,如果项目的请求量通常都是很大的话,MaxActive,InitialSize,MinIdle设置为一样即可。
         * 1. MaxActive控制上限这个是必然的
         * 2. InitialSize设置一样可以充当预热,请求来了就有连接可以用,而不是反复的超过wait时间空闲了新建。
         * 3. MinIdle在这种情况也没有必要去销毁了
         */
        try {
            //执行初始化
            druidDataSource.init();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static void insert(String insertSql) throws SQLException {
        Connection conn = null;
        Statement sm = null;
        try {
            conn = getDruidPooledConnection();
            sm = conn.createStatement();
            int row = sm.executeUpdate(insertSql);
            System.out.println("写入了 " + row + " 条数据");
        } finally {
            //这里不需要resultSet,insert语句执行完只会返回一个行数
            closeResource(conn,sm,null);
        }
    }

    public static void closePool(){
        if(druidDataSource!=null){
            druidDataSource.close();
        }
    }

    private static DruidPooledConnection getDruidPooledConnection() throws SQLException {
        return druidDataSource.getConnection();
    }

    private static void closeResource(Connection conn, Statement cm, ResultSet rs) throws SQLException {
        if(rs!=null){
            rs.close();
        }
        if(cm!=null){
            cm.close();
        }
        if(conn!=null){
            conn.close();
        }
    }
}

调用源码

package lesson5;
import java.sql.SQLException;
public class MysqlDruidTest {
    public static void main(String[] args) {
    	// \" 是转义符,可以调试查看insert执行时的变量结果
        String insert = "insert into `pop_singer`(`user_name`,`user_email`) values(\"黄家驹\",\"234@qq.com\");";
        try {
            for (int i = 0; i < 5000; i++) {
                MysqlDruid.insert(insert);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        //使用完成后销毁池
        MysqlDruid.closePool();
    }
}
关于static的说明
  1. static关键字标识的一切内容都是在第一次代码调用时,java来初始化的。
  2. static关键字声明的变量或方法,可以直接通过类名.变量 or 函数使用,不需要使用new实例化对象。
  3. private只能在当前类中使用。
  4. static关键字声明的函数不能直接访问没有使用static关键字声明的函数。
  5. static修饰的函数与变量不是线程安全的,需要时仍然需要处理线程安全问题。
  6. static代码块static { }是线程安全的,只会在类初始化时执行一次。所以我们在这里初始化DruidDataSource时可以使用。
关于final的说明
  1. final修饰的变量不能被重新赋值。
  2. final修饰的类不能被继承。
  3. final修饰的函数不能被重写
  4. final修饰的基础变量是线程安全的因为不可变。但引用对象不是,对于引用对象它保证了当前对象的地址不可变,类中的其它变量使用的是其它内存地址。
  5. final修饰的类或变量都不是线程安全的。
测试

  现在,我是很希望你通过该例子,与之前的例子(上一课)执行结果比较。同时去往写入相同的N条数据。比较它们的执行时间。往往这个数据量的大小越大,区别越大。

MyBatis框架

  数据库连接池的章节中我们解决了上一课中遗留的连接池管理问题,以及尝试着解决了反复新建关闭连接代码的问题(后面我们将让它变得更优雅)。以及每次都把数据库连接信息放到JAVA代码中也是非常的不优雅。
  现在我们来解决当查询一个表,如果这张表有十几甚至几十个(实际通常不会)字段的问题。在上一课中,我们将反复的通过ResultSet.getxxx()的方式来获取。而且只是获取,如果我们还需要将它们设置到java类对象中,我们还需要通过相同代码行数的对象.setxxx()再来返回它。

简介

  MyBatis是一个专门用于解决数据库操作领域的ORM(Object对象 Relational关系 Mapping映射)框架。从名字上我们不难看出,它除了能解决SQL操作封装的问题,还能够帮我们完成映射的过程。
  映射,就是把数据表自动的处理为我们的JAVA类对象,而不需要无数的对象.setxxx()来赋值。

引入pom.xml依赖

  既然使用别人的框架,第一时间首要去查找属于它的依赖,同时对它们的依赖有所了解(什么功能)。

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.5</version>
    </dependency>

新建mybatis文件

  1. 在工程src/main/resources下,新建一个mybatis文件夹,以及mybatis-config.xml在这里插入图片描述
  2. mybatis-config.xml的内容:
    • environments defaultenvironment id命名可以随意,在实际工作中可能是(dev你本机ortest测试机器orprod正式机器),是用于区分不同环境下使用的配置的,
    • dataSource type="POOLED"配置的是使用mybatis中默认的数据库连接池
    • mappers配置就是所有的映射关系的声明了,也都是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="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/edu_samuel?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456789"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--        加载sql映射文件-->
        <mapper resource="mybatis/PopSingerMapper.xml"/>
    </mappers>
</configuration>
  1. 在mybatis下/新建PopSingerMapper.xml
    • mapper namespace中的命名一是用来访问具体的哪个select的前缀,一个是mybatis用来缓存时(有时不需要重复执行sql到数据库,将数据缓存到java内存中直接取)的维度。
    • resultType="lesson5.PopSinger"就是咱们真正的映射所在了
<?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="test">
    <select id="selectALL" resultType="lesson5.PopSinger">
        select * from `pop_singer`
    </select>
</mapper>
  1. 在mysql中建表
CREATE TABLE `pop_singer` (
  `uid` bigint(20) 
  UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '用户 id',
  `user_name` varchar(64) COMMENT '用户名称',
  `user_email` varchar(128) COMMENT '用户 email 地址'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
  1. lesson5包下新建PopSinger类:
    • 此时可以尝试对比uid - uid ,user_name - userName,user_email - userEmail
    • *这种命名之间的定义转换是必须遵守的,在java中必须使用骆驼峰命名
    • *java中的映射类必须拥有相应的get set封装函数,否则框架无法识别
    • 也就是说首先变量名要转换正确(user_name - userName),其次get set函数也要转换正确(userName - getUserName() setUserName(String userName)决不能是setuserName,getusername等)。
package lesson5;
public class PopSinger {
    private int uid;
    private String userName;
    private String userEmail;
    public int getUid() {
        return uid;
    }
    public void setUid(int uid) {
        this.uid = uid;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserEmail() {
        return userEmail;
    }
    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }
}

它们编译后互相之间在jar or war包中的位置:

  将src与classes目录对比
在这里插入图片描述

编写测试类

  1. mybatis使用会话(Sqlsession)的概念来访问数据库,每次访问sql的一个周期都需要开启一个新的会话并且关闭。它对应JDBC中的Statement。
  2. 我们使用数据连接池时所管理的真正对象是Connection。
  3. 我们在JDBC中一个Statement周期内是可以执行很多条sql的,多条sql可以在Statement没有关闭前多次执行。(Connection的close是关闭socket:网络连接,而Statement的close是关闭流传输数据的管道
  4. mybatis中的SqlSession也是同理,可以多次执行sql,在会话期间。
  5. SqlSession底层封装的还有(它封装了我们jdbc sql处理的创建对象,处理sql,执行sql,封装返回):
    • Execute:调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句;
    • StatementHandler:使用数据库中Statement(PrepareStatement)执行操作
    • ParammeterHandler:处理SQL参数(我们之前使用占位符等等也是处理的一种)
    • ResultHandler:结果集ResultSet封装处理返回
package lesson5;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        //1.加载mybatis的核心配置文件
        String resource = "mybatis-config.xml";
        //2.获取文件的方法(请注意它是哪个包下的)
        //该方法就是以classes目录下获取文件,mybatis-config.xml在classes目录下
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //构建sqlsessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.获取sqlsession对象,用来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3.test就是namespacee,selectALL是配置的select id
        List<PopSinger> popSingers = sqlSession.selectList("test.selectALL");
        for (PopSinger popSinger : popSingers) {
            System.out.println(popSinger.getUserName());
        }
        //4.释放资源
        sqlSession.close();
    }
}

集成alibaba durid数据库连接池

  我们在框架中集成它,角色是我们去扩展它,扩展就要找到它的扩展点(官网文档)。

定义扩展
package lesson5;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
    //重写父类的方法,扩展
    @Override
    public DataSource getDataSource() {
        try {
            this.dataSource = new DruidDataSource();
            ((DruidDataSource)this.dataSource).init();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return this.dataSource;
    }
    /**
     * 为什么要在构造函数中实例化,mybatis会在执行getDataSource()之前,设定它的参数
     */
    public DruidDataSourceFactory() {
        //这里等价于super.dataSource = new DruidDataSource();
    	//当父类中的变量定义为public 或 protected,可以通过this or super访问
        this.dataSource = new DruidDataSource();
    }
}
使用&配置扩展

修改mybatis-config.xml中的内容:

  1. 通过dataSource type="lesson5.DruidDataSourceFactory"指定我们的扩展。
  2. initialSize,maxActive等和我们上面使用的一模一样。在DruidDataSourceFactory() 中this.dataSource = new DruidDataSource();就是为了在getDataSource() 之前设定这些配置的值。
<?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="dev">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <!-- <dataSource type="POOLED">  这是mybatis自带的连接池-->
            <dataSource type="lesson5.DruidDataSourceFactory">
                <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/edu_samuel?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456789"/>
                <property name="initialSize" value="10"/>
                <property name="maxActive" value="20"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--        加载sql映射文件-->
        <mapper resource="mybatis/PopSingerMapper.xml"/>
    </mappers>
</configuration>
  1. 试着对比它们的区别:
    在这里插入图片描述
总结

  框架的意义使得我们将更多的精力放在我们需要关注的业务(定义sql,处理返回的逻辑)上,而不再重复的去写代码,分散精力去管理映射、连接池等等。

反射

  在初步认识mybatis之后,我们体会到了通过一些配置文件,就可以达到完成具体功能的效果。而且是在运行时动态的去生成了这些内容。
  我们自己实现这些内容(自己写JDBC)时,我们首先需要将全部功能的.java文件准备好,然后通过maven等工具编译打包,再通过java去启动、加载、执行它,最后的这三个大概过程就叫做运行时。而我们在通过工具编译前的叫做编译时编译前
  也就是说对于框架(Mybatis等)而言,它是并不知道你有多少功能的。它知道的仅仅是它的核心内容。所以,很多时候你编写的mybatis中的文件,你只有运行测试它的时候它才会给你报错。因为这些东西它在运行时才动态的去加载。

运行时验证

  我们故意将上面的例子中的sql输入会执行错误的字符,此时mybatis本身是不会给你任何报错提示的。但如果这样的sql在workbench中必然会报错。但是这样的问题在mybatis的配置中只会在运行后报错:在这里插入图片描述

在这里插入图片描述
  其它情况:复制错误的xml标签`select```。此时报错是由于xml的执行标准,从下面而来:

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"

在这里插入图片描述

java反射

  对于List<PopSinger> popSingers = sqlSession.selectList("test.selectALL");中的test.selectALL可能都好被初步的理解。但是resultType="lesson5.PopSinger"中配置的可是一个java类(在配置中我们仅仅提供了类的全名)。mybatis加载这个java也是在运行时的。它是通过反射来做到的。

java中的动态加载

  反射(Reflection) 是java提供的,它能在运行时获取类信息、类方法信息、方法参数信息、属性信息,以及实例化使用它的一系列功能。我们实现JDBC时所使用的Class.forName就是使用反射来完成的,我想在这里已经得到解释了。除了JDBC本身的设计模式之外,java在运行之前是不知道我们使用哪家的数据库驱动的。请回顾上一篇内容第四课.

  1. 我们来体验一下自己使用反射去加载我们的mysql驱动(说白了就是一个类)。
package lesson5;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //Java运行时会将所有.class文件通过Class对象来封装
        Class<?> c = com.mysql.jdbc.Driver.class;
        //获得它的无参构造函数
        Constructor constructor = c.getConstructor();
        //通过无参构造函数来实例化它
        Object obj = constructor.newInstance();
        System.out.println(obj);
    }
}
  1. 当你运行这个程序时,控制台中会打印Loading class com.mysql.jdbc.Driver’. `,这是mysql源码中的static所输出在这里插入图片描述
调用有参的构造函数

  刚才示范了无参构造函数的调用。以最简单的代码体会了反射的动态加载特性。现在我们自己定义一个有参的构造函数,修改PopSinger类:

package lesson5;

public class PopSinger {
    public PopSinger(){
    }
    public PopSinger(int uid, String userName, String userEmail) {
        this.uid = uid;
        this.userName = userName;
        this.userEmail = userEmail;
    }
    private int uid;
    private String userName;
    private String userEmail;
    public int getUid() {
        return uid;
    }
    public void setUid(Integer uid) {
        this.uid = uid;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserEmail() {
        return userEmail;
    }
    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }
}

修改测试类,需要注意的是Object是所有类的父类,我们现在还只能通过调试来观察它的内容:

package lesson5;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionParamsTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        //没错Class.forName其实是有返回值的,在JDBC的例子中,仅仅是把它加载到了内存中,实际上后续的动作是java去完成了
        Class<?> c = Class.forName("lesson5.PopSinger");
        Constructor constructor = c.getConstructor(int.class, String.class,String.class);
        //这里必须要实例化,因为我们后续调用函数必须是实例化了
        Object obj = constructor.newInstance(1,"2","3");
        //输出
        System.out.println(obj);
    }
}

在这里插入图片描述

调用函数
package lesson5;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionMethodsTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> c = Class.forName("lesson5.PopSinger");
        Constructor constructor = c.getConstructor(int.class, String.class,String.class);
        //这里必须要实例化,因为我们后续调用函数必须是实例化了
        Object obj = constructor.newInstance(1,"2","3");
        //获取你自定义的全部方法,可以运行查看控制台
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        //获取具体的某个函数
        Method method = c.getMethod("getUserName");
        //调用getUserName,使用刚才实例化的obj对象
        Object result = method.invoke(obj);
        //输出2
        System.out.println(result);
    }
}
总结

  请注意前面示例中的List<PopSinger> popSingers = sqlSession.selectList("test.selectALL");,sqlSession其本身只是一个接口,它定义了非常多的默认的访问数据库的模板。并且同时它是没有任何实现的。为什么这句代码编译能通过?sqlSession.selectList返回的又不是这个类型,请回忆泛型第三课
  我们在PopSingerMapper.xml配置文件中配置的是resultType="lesson5.PopSinger",而返回类型却是一个List。它本身内部就是通过模板(selectALL)。我们调用sqlSession接口方法时,通过反射获取到了它的入参和返回值。此时知道了要返回一个List泛型,也知道了SQL的入参。

为什么一定要提设计模式

  和JDBC一样,它们实现的原理息息相关,并且能够在这样的场景下顺带着学习更多主线的知识点。

模板方法设计模式

  我们在讲mybatis的构造时,提到过Executor,SqlSession所有真正的执行都是他来完成的。它是一个abstract class BaseExecutor。抽象类情回顾[第二课],如果相同的执行我们需要其中某些步骤有些许不同时,就可以采用这种设计模式。比如有时需要缓存,有时并不需要缓存。但是前后的大部分操作都是类似的,这种时候就需要局部相应的不同实现。

Mybatis中的BaseExecutor(模板方法的具体体现)

&esmp;&esmp;它在BaseExecutor定义了一个没有任何实现的模板方法,交由子类去实现。
在这里插入图片描述
  而它自己本身则实现具体框架中的内容,通过这样一个过程赋予了框架意义->扩展:
在这里插入图片描述

所以这个设计模式的理解难度并不大,其本身也非常的简单。关键用对了场景,它同样也能起到非常大的作用。

改变mybatis的调用方式

  还记得我们定义的mapper吗,在之前的示例中,我们通过sqlSession.selectList("test.selectALL")来完成了查询数据库的功能。但是这种方式它仅仅是通过读取其中的sql去执行而已。也就是说目前为止mybatis的使用并不是它的完整形态。在这里插入图片描述
  如果有成千上万个 “test.selectALL” 配置,我们的代码中全部都是这样的字符串,现在我们要将它改变为java类调用的方式而不是字符串:

  1. 定义Mapper接口
package mybatis;
import lesson5.PopSinger;
import java.util.List;
public interface PopSingerMapper {
    List<PopSinger> select();
}
  1. 修改PopSingerMapper.xml 请注意注释位置,此时是Mapper接口的包全名
<?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="test">-->
<mapper namespace="mybatis.PopSingerMapper">
    <select id="select" resultType="lesson5.PopSinger">
        select * from `pop_singer`
    </select>
</mapper>
  1. 此时请注意它们的包路径,虽然一个在src/main/java/mybatis一个在src/main/resources/mybatis下,但是他们编译后的路径在同一个目录下。也就是说对于编译后而言src/main/java和src/main/resources是多余的。在这里插入图片描述

  2. 修改mybatis-config.xml,此时我们通过package name的方式扫描以编译结果为准的mybatis目录,去发现Mapper类和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>
    <!-- 开启驼峰映射 否则无法解释userName到mysql的映射 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <!-- <dataSource type="POOLED">  这是mybatis自带的连接池-->
            <dataSource type="lesson5.DruidDataSourceFactory">
                <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/edu_samuel?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456789"/>
                <property name="initialSize" value="10"/>
                <property name="maxActive" value="20"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--        加载sql映射文件-->
<!--        <mapper resource="mybatis/PopSingerMapper.xml"/>-->
        <package name="mybatis"/>
    </mappers>
</configuration>
  1. 修改我们的执行类,此时我们已经是通过真正java类来完成调用了,而不是每次都要记录xml中配置的namespace和id。并且请注意,现在我们返回的并不是List集合了,而是PopSingerMapper popSingerMapper接口对象。
package lesson5;
import mybatis.PopSingerMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        //1.加载mybatis的核心配置文件,获取sqlsessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.获取sqlsession对象,用来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3.执行sql
//        List<PopSinger> popSingers = sqlSession.selectList("test.selectALL");
        PopSingerMapper popSingerMapper = sqlSession.getMapper(PopSingerMapper.class);
        List<PopSinger> popSingers = popSingerMapper.select();
        for (PopSinger popSinger : popSingers) {
            System.out.println(popSinger.getUserName());
        }
        //4.释放资源
        sqlSession.close();
    }
}

代理设计模式

  通过刚才的示例中,我们是如何来通过一个接口来实现调用的呢?它并没有任何实现不是吗?
  如果你调试了刚才返回的PopSingerMapper接口对象,你会发现它的名字叫org.apache.ibatis.binding.MapperProxy@地址

jdk动态代理

  听名字也就知道java自带的,所以不需要引入第三方包来实现它。在jdk动态代理中,我们必须定义一个接口。以及实现这个接口的类,并且将此类作为被代理的对象,通过反射来完成。

  1. 定义一个接口和实现类
package lesson5;
public interface JDKAgencyInterface {
    void test();
}
package lesson5;
public class JDKAgencyClass implements JDKAgencyInterface {
    @Override
    public void test() {
        System.out.println("这是被代理的对象");
    }
}
  1. 通过反射java.lang.reflect包来生成代理后的对象JDKAgencyInterface after并调用
package lesson5;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKAgencyTest {
    public static void main(String[] args) {
        //实例化一个对象
        final JDKAgencyClass jdkAgencyClass = new JDKAgencyClass();
        /**
         * 1.这里会通过Proxy.newProxyInstance生成另外一个类,这个类和是反射包中的。
         * 2.JDKAgencyClass.class.getClassLoader()是使用JDKAgencyClass共同的ClassLoader来生成这个类
         * 3.JDKAgencyClass.class.getInterfaces()获取JDKAgencyClass类实现的接口集合
         * 4.new InvocationHandler(),动态定义实现类型
         */
        JDKAgencyInterface after = (JDKAgencyInterface) Proxy.newProxyInstance(
                JDKAgencyClass.class.getClassLoader(),
                JDKAgencyClass.class.getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                System.out.println("调用之前");
                result = method.invoke(jdkAgencyClass);
                System.out.println("调用后");
                return result;
            }
        });
        after.test();
    }
}

总结

  你完全可以理解为刚才的JDKAgencyClass在mybatis中,实际上就是那个被动态生成出来的org.apache.ibatis.binding.MapperProxy。它实现了你的接口,并且可以拦截你的调用。所以它本来就不需要你有调用内容存在。它只需要通过代理接口去执行sql,然后将执行的结果返回即可。也就是说对于Mybatis而言,result = method.invoke(jdkAgencyClass);这句代码,完全是被注释掉的。

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值