1. 程式人生 > >《俗人筆記》之《mybatis與spring的自我小結》

《俗人筆記》之《mybatis與spring的自我小結》

框架的真諦總結:非侵入式程式設計,及不在原始碼上修改,也能實現效果,這裡是動態代理的絕佳展示,還有就是集中管理

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>