处理关系:
resultSet:https://blog.csdn.net/u012060033/article/details/81121684
注意这个Element是如何得到的。通过全限定类名反射得到的反射得到的。
封装的思路:
selectList这个也是反射进行的封装的。
Map的key是String类型的:Dao是全限定的类名.方法名。
Map的value是:就是Mapper对象,包含String sql和String domainClassPath(结果实体类的全限定类名)。
查询所有的分析。
代理对象和被代理对象使用相同的类加载器。
----------------------------------------------------------------------01-------------------------------------------------------------
实现的功能,整体的结构。
关注的是代理对象和设计模式。
------------------------------------------------------02--------------------------------------------------------
我们自定义mybatis:
代码:G:\CODE_MY\heimabase\mybatis\01\day01_eesy_04mybatis_design
第一步:读取配置文件
第二步:写建造者用于创建sqlSessionFactory对象
第三步:sqlSessionFactory
第四步:创建SqlSession
泛型要先声明再使用,声明在返回值之前。
-------------------------------03-------------------------------
第五步:拷贝这个工具类。
这个配置类咋判断的是用的xml还是注解?
解析映射类的xml。
第六步:这个配置类有返回值,我们创建返回值
第七步:定义Mapper
第八步:创建SqlsessionFactory的步骤,注意cfg是主配置文件的流。
第九步:创建一个DefaultSqlSessionFactory的实体类,实现SqlSessionFactory接口:
第十步:实现SqlSession接口
‘
这里的动态代理:代理谁就用谁的类加载器。
第十一步:创建代理
第十二步:调用工具类查询所有。
第十三步:创建DataSourceUtil
------------------------------------------------------------------------------------------
SqlMapConfig.xml有什么信息呢?连接的基本信息,映射的xml信息。
我们看下配置文件是如何解析的?
配置文件。
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
build方法:
SqlSessionFactory factory = builder.build(in);
进入这个build方法:
public SqlSessionFactory build(InputStream config){
Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(cfg);
}
重点看这个方法:
public static Configuration loadConfiguration(InputStream config){}
返回cfg封装:
1.获得配置文件的连接信息
2.获得Map<String,Mapper> mappers一个IUserDao.xml是一个mappers
这个mappers的key就是全类名.方法名 value就是对象查询语句和返回值的类型
注意下这个方法:传入的是com/itheima/dao/IUserDao.xml,读取里面的配置信息就是sql语句。
private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {}
接下来就是创建SqlSessionFactory:
public SqlSessionFactory build(InputStream config){
Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(cfg);
}
接下来产生sqlSession对象:
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
SqlSession对象里面是cfg和connection和getMapper也就是反射的方法。
接下来获得反射的对象
IUserDao userDao = session.getMapper(IUserDao.class);
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
}
注意看那个
new MapperProxy(cfg.getMappers(),connection)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.获取方法名
String methodName = method.getName();
//2.获取方法所在类的名称
String className = method.getDeclaringClass().getName();
//3.组合key
String key = className+"."+methodName;
//4.获取mappers中的Mapper对象
Mapper mapper = mappers.get(key);
//5.判断是否有mapper
if(mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}
//6.调用工具类执行查询所有 里面就是简单的preparement方法。
return new Executor().selectList(mapper,conn);
}
再次调用方法,其实是调用的invoke方法,调用selectList方法。
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
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);
}
}
--------------------------------------------------03----04-----05-------------------------------------------------------------------
此时我们在sqlSession写下创建代理对象的方法:
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
}
经典的动态代理:传入的参数是classLoader,被代理的类,innovicationHandler。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.获取方法名
String methodName = method.getName();
//2.获取方法所在类的名称
String className = method.getDeclaringClass().getName();
//3.组合key
String key = className+"."+methodName;
//4.获取mappers中的Mapper对象
Mapper mapper = mappers.get(key);
//5.判断是否有mapper
if(mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}
//6.调用工具类执行查询所有
return new Executor().selectList(mapper,conn);
}
注意经典的反射的一个问题:
method.invoke(target, args)
public static void reflectPrivateMethod() {
try {
Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Method methodBook = classBook.getDeclaredMethod("declaredMethod",int.class);
methodBook.setAccessible(true);
Object objectBook = classBook.newInstance();
String string = (String) methodBook.invoke(objectBook,0);
Log.d(TAG,"reflectPrivateMethod string = " + string);
} catch (Exception ex) {
ex.printStackTrace();
}
}
表示一个方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.获取方法名
String methodName = method.getName();
//2.获取方法所在类的名称
String className = method.getDeclaringClass().getName();
//3.组合key
String key = className+"."+methodName;
//4.获取mappers中的Mapper对象
Mapper mapper = mappers.get(key);
//5.判断是否有mapper
if(mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}
//6.调用工具类执行查询所有 里面就是简单的preparement方法。
return new Executor().selectList(mapper,conn);
}
看下这里传入的是什么?
创建了这个代理对象,以后不管对象用什么方法都走代理方法。
注意:这里根据方法的名字去找对应的查询:
----------------------------------------------------------------------06--------------------------------------------------------------
基于注解:
第一步:修改配置文件
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<!--<mapper resource="com/itheima/dao/IUserDao.xml"/>-->
<mapper class="com.itheima.dao.IUserDao"/>
</mappers>
第二步:在Dao接口写上注解
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 查询所有操作
* @return
*/
@Select("select * from user")
List<User> findAll();
}
第三步:没有注解报错,我们自定义注解
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
* 查询的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
/**
* 配置SQL语句的
* @return
*/
String value();
}
第四步:我们看下解析的代码,反射注解的方式
/**
* 根据传入的参数,得到dao中所有被select注解标注的方法。
* 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息
* @param daoClassPath
* @return
*/
private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
//定义返回值对象
Map<String,Mapper> mappers = new HashMap<String, Mapper>();
//1.得到dao接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//2.得到dao接口中的方法数组
Method[] methods = daoClass.getMethods();
//3.遍历Method数组
for(Method method : methods){
//取出每一个方法,判断是否有select注解
boolean isAnnotated = method.isAnnotationPresent(Select.class);
if(isAnnotated){
//创建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;
}
用反射解析的注解
取出注解的value属性。就是select*from
看下注解是如何工作的,就是反射注解,这个一定要注意了。
-----------------------------------------------------------------06-------------------------------------------------------------------
小总结:
我们看下反射的写法和生成的文件:这个是h的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("<<<<<<<<<纯手写jdk动态代理日志开始>>>>>>>>>>");
Object result = method.invoke(target, args);// 使用java的反射执行
System.out.println("<<<<<<<<纯手写jdk动态代理结束>>>>>>>>>>");
return result;
}
这个是方法的invoke方法:
public void order() throws Throwable {
// 反射获取如何获取真实目标方法呢
Method orderMethod = OrderService.class.getMethod("order", new Class[]{});
this.h.invoke(this, orderMethod, null);
}
最后反射的写法:
public static void main(String[] args) throws Throwable {
/* OrderService orderService = new $Proxy0(new MyJdkInvocationHandler(new OrderServiceImpl()));
orderService.order();*/
OrderService orderService = (OrderService) MyProxy.newProxyInstance(new JavaClassLoader(), OrderService.class, new MyJdkInvocationHandler(new OrderServiceImpl()));
orderService.order();
}
而JDK的动态代理是如何实现的呢?
第一步:
public class JdkInvocationHandler implements InvocationHandler {
/**
* 被代理类对象 目标代理对象
*/
private Object target;
public JdkInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>>jdk打印订单日志开始:proxy:"+proxy.getClass().toString());
Object reuslt = method.invoke(target, args);// java的反射机制执行方法 执行目标对象的方法
System.out.println(">>>jdk打印订单日志结束");
return reuslt;
}
/**
* 使用jdk动态代理创建代理类
*
* @param <T>
* @return
*/
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
第二步:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
OrderService proxy = new JdkInvocationHandler(new OrderServiceImpl()).getProxy();
proxy.order2();