‘ 框架 ’ 入门
-
框架:软件开发中的一套解决方案,半成品,不同的框架解决不同的问题。
好处:封装了很多细节,开发者可以使用极简的方式实现功能,提高开发效率。
-
三层架构:
- 表现层:展示数据——SpringMVC
- 业务层:处理业务需求
- 持久层:与数据库交互——mybatis
Spring 框架 : IOC & AOP
-
持久层技术解决方案:
- JDBC 技术:
- Connection
- PreparedStatement
- ResultSet
- Spring 的 JdbcTemplate:Spring中对jdbc的简单封装
- Apache 的 DBUtils:和 Spring 的 JdbcTemplate很像,也是对jdbc的简单封装
以上这些都不是框架,JDBC是规范,Spring的 JDBCTemplate 和 Apache 的 DBUtils 都只是工具类
- JDBC 技术:
MyBatis 框架概述
- mybatis:持久层框架,java编写,封装了jdbc操作的很多细节,开发者只需要关注sql语句本身,无需关注注册驱动、创建连接等繁杂过程,它使用 ORM 思想实现了结果集的封装。
- ORM:Object Relational Mapping 对象关系映射
- 简单说就是把数据库表和实体类及实体类的属性对应起来,让我们可以通过操作实体类来实现操作数据库表。
mybatis的入门
-
mybatis环境搭建:
- 创建maven工程并导入坐标(依赖)
- 创建实体类和dao的接口
- 创建mybatis的主配置文件SqlMapConfig.xml
- 创建映射配置文件IUserDao.xml
-
环境搭建注意事项:
- 创建IUserDao.xml和IUserDao.java时名称一致是为了和我们之前的知识保持一致。在Mybatis中它把持久层的接口名称和映射文件也叫做:Mapper。所以IUserDao和IUserMapper是一样的。
- mybatis的映射配置文件必须和dao接口的包结构相同。
- 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名。
- 映射配置文件的操作配置(select),其id属性的取值必须是dao接口的方法名。
当我们遵从2、3、4之后,我们在开发中就无需再写dao接口的实现类。
-
mybatis入门案例
- 读取配置文件
- 创建SQLSessionFactory工厂
- 创建SqlSession对象
- 创建Dao接口的代理对象
- 使用代理对象执行方法
- 释放资源
注意:不要忘记在映射文件配置中告知mybatis要封装到哪个实体类中
配置方式:指定实体类的全限定类名(ResultType)
mybatis基于注解的入门案例:
把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句;
同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名;
明确:在实际开发中一般都是越简单越好,都是采用不写dao实现类的方式,但是mybatis支持写dao实现类。
-
自定义mybatis分析:
-
mybatis在使用代理dao的方式实现增删改查时做什么事情:
- 创建代理对象
- 在代理对象中调用
selectList()
-
自定义mybatis能通过入门案例看到的类:
-
class Resources class SqlSessionFactoryBuilder interface SqlSessionFactory interface SqlSession
-
-
动态代理
代理模式:代理模式给某个对象提供一个代理对象,并由代理对象控制对原对象的引用,通俗来讲代理模式就是我们生活中常见的中介,动态代理和静态代理的区别在于静态代理需要我们手动的去实现目标对象的代理类,而动态代理可以在运行期间动态的生成代理类。
- 有一个打印机的类
public class Printer{
public void print(){
System.out.println("打印!");
}
}
如果下想在打印之前先记录一下日志该怎么做?
最简单的方法:在打印功能前面直接加上记录日志的功能,如下:
public class Printer {
public void print(){
System.out.println("记录日志!");
System.out.println("打印!");
}
}
但是这样做修改了打印机的源代码,破坏了面向对象的开闭原则,有可能影响到其他功能。
- 在上述背景下,可以考虑新建一个类,继承打印机的类,重写打印机的print方法,提供记录日志的功能
public class LogPrinter extends Printer {
public void print(){
System.out.println("记录日志!");
System.out.println("打印!");
}
}
之后需要打印机的时候使用这个类就好。
- 在上面这个解决方案的基础上进一步优化:
抽象出一个接口,Printer类实现该接口:
public interface IPrinter {
void print();
}
创建打印机代理类也实现该接口,在构造函数中将打印机对象传进去,实现接口的打印方法调用打印机对象的打印方法并在前面加上记录日志的功能:
public class PrinterProxy implements IPrinter{
private Iprinter printer;
public PrinterProxy(){
this.printer = new Printer();
}
@Override
public void print(){
System.out.println("打印日志");
printer.print();
}
}
测试:
public class Test{
public static void main(String[] args){
PrinterProxy proxy = new PrinterProxy();
proxy.print();
}
}
以后我们就可以直接实例化PrinterProxy对象调用他的打印方法了,这就是静态代理模式,通过抽象接口让程序的扩展性和灵活性更高了。
考虑一下,如果打印机类中还有别的方法,也需要加上记录日志的功能,就不得不将记录日志的功能写N遍。
进一步,如果我还有电视机、电冰箱的类中所有方法也需要加上记录日志的功能,那么要重复的地方就更多了。
鉴于上述背景,动态代理就诞生了。
-
要想不重复写记录日志的功能,考虑让这些代理类的对象自动生成。JDK提供了InvocationHandler接口和Proxy类,借助这两个工具可以达到我们想要的效果。
- InvocationHandler接口:
/** * Object proxy:被代理的对象 * Method method:要调用的方法 * Object[] args:方法调用时所需要的参数 */ public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
接口里只有一个方法invoke,这个方法非常重要。
- Proxy类:它里面有个很重要的方法 newProxyInstance:
/** * ClassLoader loader:被代理的对象的类加载器 * Class<?>[] interfaces:被代理类的全部接口 * InvocationHandler h:实现InvocationHandler接口的对象 */ public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
调用Proxy的newProxyInstance方法可生成代理对象
- 动态代理如下:
实现一个类,该类用来创建代理对象,他实现了InvocationHandler接口:
public class ProxyHandler implements InvocationHandler{ private Object targetObject;//被代理的对象 //被代理的对象传入获得他的类加载器和实现接口作为Proxy.newProxyInstance方法的参数 public Object newProxyInstance(Object targetObject){ this.targetObject = targetObject; //targetObject.getClass().getClassLoader():被代理对象的类加载器 //targetObject.getClass().getInterfaces():被代理对象的实现接口 //this 当前对象,该对象实现了InvocationHandler接口所以有invoke方法,通过invoke方法可以调用被代理对象的方法 return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),this); } //该方法在代理对象调用方法时调用 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ //在这里可以通过判断方法名来决定执行什么功能 System.out.println("记录日志"); //调用被代理对象的方法 return method.invoke(targetObject,args); } }
重点分析这段代码:
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),this);
动态代理对象就是通过调用这段代码被创建并返回的。
测试:
public class Test{ public static void main(String[] args){ ProxyHandler proxyHandler = new ProxyHandler(); IPrinter printer = (IPrinter) proxyHandler.newProxyInstance(new Printer()); printer.print(); } }
当执行printer.print();时会自动调用invoke方法,创建代理对象时是通过
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),this);
来创建的,方法的第三个参数this是实现了InvocationHandler接口的对象,而InvocationHandler接口里面有invoke方法。
将被代理的对象作为参数传入就可以执行里面的任意方法,所有的方法调用都通过invoke来完成,不用对每个方法进行处理。