框架的真谛总结:非侵入式编程,及不在源代码上修改,也能实现效果,这里是动态代理的绝佳展示,还有就是集中管理
mybatis的期望:减少sql语句与java代码的耦合,sql就干sql,所以才用mapper配置文件替代接口的实现类,为了让mapper配置文件与接口连接,所以使用动态代理,这样做不但可以减少耦合,还便于集中管理,
spring的期望:减少各层间的耦合度,通过工厂模式+反射+xml来实现,工厂在于创建对象,反射在于根据类来返回更具通用性的object,这些都交给spring就行
mybatis
mybtis与传统的区别:传统dao执行数据库,都是通过接口要求来对应实现功能并书写对应的sql语句,在mybatis中就可以通过sqlseesion来实现crud,可以
自己定义sql语句
探索一:创建mapper文件,并在里面按照mybatis的规范写数据执行语句,然后将原先的实现类改换成调用mapper里面方法,这里中介是sqlseesion
问题:这里的sqlssesion怎么获取,通过sqlseesion工厂来获取,其实通过各种方法自己手动传入或者是就在实现类中就创建都一样,这里就
构造方法传入吧
private SqlSession sqlSession;
public UserDaoImpl(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
//通过sqlseesion获取对应mapper的方法
sqlSession.insert("UserDaoMapper.addUser", user);
sqlSession.commit();
获取:
//指定mybatis的核心配置文件
String source="mybatis-config.xml";
//通过mybatis提供的工具类读取配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(source);
//产生sqlseesion工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//获取会话对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);//加入true可以自动提交事务,否则无法执行
userDao = new UserDaoImpl(sqlSession);
问题又出现了:每次执行时都会创建一个对象,而且是通过接口的实现类里面再通过sqlseesion回去对应mapper文件的方法,真他妈多此一举!
所以首先要干掉接口的实现类,并且希望直接通过接口来连接mapper文件,就相当于mapper此时就是接口的实现类
解决方法:
之前的mapper的工作空间可以乱写,但此时就不行了,此时空间名必须是接口的全路径,要让接口与之形成映射,及此时的各方法id就是接口方法名,再通过接口来调用方法时,就可以用动态代理技术了(这里是怎么使用动态代理的?所谓动态代理,这里是jdk代理,主要有代理接口),将mapper文件直接让sqlseesion读取,然后接口直接获取就行
探索二:
private UserMapper mapper;//此时的usermapper就是接口,只不过换了个名字而已(为什么要用接口,这里就是面向接口编程思想了)
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")).openSession(true);
mapper = sqlSession.getMapper(UserMapper.class);
技术核心:
resultmap:
对返回值的规范,以前直接是resultype,这有很大的局限性,即只能是对象类和基本类型,但问题来了,如果出现实体类里面的属性与数据库表不一致怎么办,或者
是如果返回值很混乱怎么办(就是多表联查时)
//一对一表关系
<resultMap id="oneToOne" type="Order" autoMapping="true">
<result property="id" column="id"/>//这里这样写是为了避免多表查询时id混乱,这里明确一下,这里其实也是解决匹配不一致的方法,在这里明确一下就行
<association property="user" javaType="User" autoMapping="true">
<result property="id" column="user_id"></result>
</association>
</resultMap>
//在上面基础上又一对多
<resultMap id="oneToMany" type="Order" autoMapping="true" extends="oneToOne">
<collection property="orderdetails" javaType="list" ofType="Orderdetail" autoMapping="true">
<result property="id" column="orderdetail_id"></result>//这里其实要注意了,这里的orderdetail_id其实在原查询中是没有的,因为在order表中没有其对应,orderdetail的id也不是外键
,这就很尴尬了,所以查询时自己自定义下
</collection>
</resultMap>
<select id="selectOrderWithUserDetailsItemByOrderNumber" resultMap="manyToMany">
select * ,tod.id as orderdetail_id from tb_order tor left join tb_user tu on tor.user_id = tu.id
left join tb_orderdetail tod on tod.order_id = tor.id
left join tb_item ti on tod.item_id = ti.id
where order_number = #{orderNumber}
</select>
//多对多
<resultMap id="manyToMany" type="Order" autoMapping="true" extends="oneToOne">
<collection property="orderdetails" javaType="list" ofType="Orderdetail" autoMapping="true">
<result property="id" column="orderdetail_id"></result>
<association property="item" javaType="Item" autoMapping="true">
<result property="id" column="item_id"></result>
</association>
</collection>
</resultMap>
懒加载:
就是按需加载,虽然是都可以在一起查询,但是可以在需要时再拼接查询
<!--开启延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭立即加载-->
<setting name="aggressiveLazyLoading" value="false"/>
<resultMap id="lazyLoad" type="Order" autoMapping="true">
<result property="id" column="id"></result>
<association property="user" javaType="User" select="selectUserById" column="user_id">
</association></resultMap>
<!--延迟加载,根据订单号查询订单信息以及下单人信息-->
<select id="lazySelectOrderUserByOrderNumber" resultMap="lazyLoad">
select * from tb_order where order_number = #{orderNumber}
</select>
<select id="selectUserById" resultType="User" >
select * from tb_user where id = #{user_id}
</select>
小注意:
常规查询返回值为list时,可以resultType直接返回对应实体类型,但在多表联查时,如果是一对多,其对应属性肯定是list关系,所以在javaType中要先写list,在oftype中再写对应的实体类
懒加载时,在实体对应属性时,相较之前还是有区别的,比如,一对一之前是
现在变为 此时的select就是懒记载对象,column就是懒加载查询条件值
mybatis的配置文件属性是有顺序的,从上到下是:properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?
spring
传统配置:
减少每次调用时new对象,减少各层之间的依赖性,避免因为依赖层的改动而都改动(比如父接口改动的话,其实现类都要改动,因为子类可以多实现及对应多个接口,在这里想解决的问题是,根据不同的接口,希望只通过一个方法将子实现类的方法给不同接口,这里就是反射了,获得子类的类对象,返回object对象,即可自定义接口对象,此时父接口创建对应实现类,取到对应后bean后,之后要换实现类的话直接在配置文件的bean地址就行)
总结:
交给spring工厂的好处有两个:反射的使用有利于实现类更改后,可以按需求返回对应接口对象,因为它返回的object,具体自己配,这样做便于通用性,为了实现类交换容易,通过配置文件直接更改bean地址就行,这样解决硬编码,实现动态传入,即通过一个getbean("")方法传入不同参数从而获得不同实现类
拓展:
工厂模式:只为获得所需实例,不在意过程可以依次往上提升,最终都到application中配置对应的bean,spring工厂的作用就是代理创建对象和管理依赖
<bean class="dao.userDaoImpl" id="userDao"/>
<bean class="service.userServiceImpl" id="userService">
<property name="userDao" ref="userDao"></property>
</bean>
此时仍要在servieImpl里面加入dao的set方法
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
/* userService userService = (service.userService) applicationContext.getBean("userService");*/ 这里实通过id来获取的
userService userService = applicationContext.getBean(service.userService.class);//这里则是通过父接口类来获取的,便于扩展,但前提是只能有一个实现类,要不然无法对应
userService.login();
注解配置:
更为方便的配置,比如可以不用配置bean和依赖,
配置bean
@Component(“springService”)//可以省略括号
@Control(“springControl”)//可以省略括号,功能一样,只是换了个名字而已
@Service(“springService”)//可以省略括号,功能一样,只是换了个名字而已,便于区别
@Responlity(“springDao”)//可以省略括号,功能一样,只是换了个名字而已
配置依赖
@Autowired
@Qualifier(“dao”)//如果出现多实现,则通过id找
传统和注解可以混合配置,比如bean自己配,依赖注解,但反过来就不行
监听器
就是将读取applicationContext的任务和值交给spring和servletContext,不用每次都创建
// 传统读取,但每次都要new来创建,有点浪费,希望都公用一个就行,这时就考虑到融汇项目的servletCongtext
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//先到web.xml里面配置
<!--spring监听器监听web容器的启动,并且创建spring容器以属性的方式绑定到servletContext中-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
//通过上诉配置,项目在开启时就已经创建一个spring工厂了,并加入servletContext中,只要直接获取就行,但还是太长了
ApplicationContext applicationContext = (ApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
//这里就直接使用spring提供好的工具类就行
//ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
TestService testService = (TestService) applicationContext.getBean("testService");
testService.dodo();
aop
核心属性:
tagert:对象 拦截点:对象里面的所有方法 切点:所要修改的方法 通知:就是拦截到的切点执行的操作 切面:切点+通知 织入:整个代理的过程
动态代理:
jdk代理(前提是有代理接口)
return Proxy.newProxyInstance(target.getClass.getClassLoder,target.getClass.getInterfaces,new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("addInfo")) {
System.out.println("注意啦!");
}
Object invoke = method.invoke(target, args);//调用原方法,执行并返回
return invoke;})
cglib代理(无代理接口)
首先要实现MethodInterceptor接口,从而实现invoke方法
Enhancer e=new Enhancer();//获得代理器
//获得目标
e.setSupperClass(target.getClass)
//处理方法
e.setCallBack(this)
//生成代理对象并返回
e.create()
集体方法执行代码,参数和jdk的差不多
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("addInfo")) {
System.out.println("大注意啦!");
}
return method.invoke(target,objects);
}
配置:
配置目标
配置通知
配置aop
旧aop是可以对方法动态代理的
<aop:config>
//配置切点
<aop:pointcut id="mucut" expression=(bean("*Service"))/>
//配置切面
<aop:advistor advice-ref="切面id" pointcut-ref="mycut"/>
</aop:config>
aspect代理可以执行更多
<aop:config>
<aop:aspect ref="切面id">
//配置切点
<aop:pointcut id="mucut" expression=“bean("*Service")”/>
前置通知:before 参数:joinpoint 对权限日志拦截
<aop:before method="" pointcut-ref="" />
<aop:before method="" pointcut=“bean("*Service")” />//这也是一种写法,但不太好,不便于统一管理
后置通知:after 参数:joinpoint ,object 对方法执行后有返回值
<aop:after-returning method="" pointcut-ref="" returning="object"/>
最终通知:after 无参 释放资源
<aop:after method="" pointcut-ref="" />
环绕通知:around 参数:proceedingpoint 可以执行动态代理
<aop:around method="" pointcut-ref="" />
抛出通知:after-throwing 参数:joinpoint,throwable 对方法问题抛出
<aop:after-throwing method="" pointcut-ref="" throwing="thorowable"/>
</aop:aspect>
</aop:config>
ioc
创建:反射
赋值:
aop:切面对象
切点:默认jdk代理拦截类,只可以用父接口方法,可以改成cglib代理实现类
注解配置
在通知类上注解配置bean,还要额外加个@Aspect,在xml配置文件中再打开<aop:aspect-autoproxy proxy-target-class=“true”/>,打开cglib代理
jdbctemplate
法一:
new DriverManagerDataSource()获得datasource,再new jt
法二:
在dao曾set注入
前提:先配内置数据源,bean的类是DriverManagerDatasource
再配bean类是jdbctemplate,再内配属性datasource
最后将bean类为dao内配置属性template
自动注入
不用dao的bean内配置了
注意: 这两个方法是在不继承jdbcDaoSupper
法三:继承jdbcdaosupper,直接调用getJT就行
但是要再dao的bean内配置属性datasouce,因为继承jdbcsupper了嘛
事务通知
开启扫描<context:compent-scan base-package=""/>
开启数据事务管理平台
bean类是datasourcethransaction,再将datasource属性内配置
配置通知
<tx:advicen id="txAdvice">
<tx:attributes>
<tx:method name="" />
</tx:attribute>
</advice>
配置切面
<aop:config>
切点
<aop:pointcut id="mycut" experssion="bean(*Service)"/>
配置通知
<aop:advistor advice-ref="txAdvice" pointcut-ref="mycut"/>
</aop:config>