Spring AOP 通知
-
前置通知
在目标方法前切入追加功能。采用@Before,方法定义格式如下:
@Before("切入点表达式") public void xxx(){...}
-
后置通知
在目标方法后切入追加功能,如果目标方法抛异常不会切入功能。采用@AfterReturning
@AfterReturning("切入点表达式") public void xxx(Object retval){...}
-
最终通知
在目标方法后切入追加功能,目标方法有无异常都会切入功能。采用@After
@After("切入点表达式") public void xxx(){...}
-
异常通知
在目标方法抛出异常后切入追加功能。采用@AfterThrowing
@AfterThrowing(...) public void xxx(Exception e){}
-
环绕通知
在目标方法前和后切入追加功能。采用@Around
public Object xxx(ProccedingJoinPoint pjp){ //前面切入 Object obj = pjp.proceed();//调用目标方法 //后面切入 return obj; }
案例1:采用AOP实现异常处理
需求:当服务调用中发生异常,将异常记录到日志文件中。
-
切面
将异常信息记录到日志文件FileWriter、PrintWriter或log4j
-
切入点
给所有controller方法within(cn.xdl.controller..*)
-
通知
发生异常后,异常通知 @AfterThrowing
-
实现过程
-
编写切面组件,追加异常通知方法
@Component @Aspect public class ExceptionBean { @AfterThrowing(throwing="ex",pointcut="within(cn.xdl.controller..*)") public void handle(Exception ex){ try { FileWriter fw = new FileWriter("E:\\spring_07_error.log", true); PrintWriter out = new PrintWriter(fw); out.println("-----------发生了异常------------"); out.println("-异常类型:"+ex); out.println("-发生时间:"+new Date()); out.println("-异常详情:"); StackTraceElement[] stes = ex.getStackTrace(); for(StackTraceElement s : stes){ if(s.toString().contains("cn.xdl")){ out.println(s.toString()); } } out.flush(); out.close(); fw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
-
追加@Component、@Aspect、@AfterThrowing标记
-
案例2:AOP实现原理(动态代理模式)
原理:Spring容器配置AOP,容器返回的BookContorller对象实际是一个动态代理技术生成子类对象(CGLIB技术)。动态代理类将BookController对象所有方法重写,在重写时加入BookController功能和切入新功能(切面组件)。
扩展:CGLIB技术使用方法(了解)
-
根据现有组件创建代理对象
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(bean.getClass());//将bean类型做父类 enhancer.setCallback(this);//注册回调函数 return enhancer.create();
-
每次调用代理对象方法,都会采用回调机制调用原组件方法,可以在回调方法中做扩展操作
public class CglibProxyFactoryBean implements MethodInterceptor{ public Object intercept( Object obj, Method method, Object[] args, MethodProxy arg3) throws Throwable { //切入目标方法前面执行的逻辑 System.out.println("回首2017"); Object retval = method.invoke(target, args);//调用目标组件对象的method方法 //切入目标方法后面执行的逻辑 System.out.println("展望2018"); return retval; } }
Mybatis框架
简介
MyBatis前身是iBatis.MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
Mybatis框架可以简化数据库访问操作,对以前JDBC技术做了封装操作。主要封装功能如下:
- 封装了获取连接、创建statement、执行sql、释放连接整个过程
- 封装了SQL参数映射过程(sql使用#{属性名})
- 封装了查询结果映射成实体对象过程(名称对应)
- 封装了Mapper映射器接口,可以动态生成Dao对象
- 封装了很多辅助功能,比如缓存、延迟加载
- 封装了多表查询关联映射功能
框架结构
案例3:使用MyBatis对USER表操作
-
搭建mybatis环境
- 引入mybatis.jar和ojdbc.jar
- 引入src/sqlmap-config.xml
sqlmap-config.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> <!-- 指定类型别名 --> <typeAliases> <typeAlias type="cn.xdl.entity.User" alias="user"/> </typeAliases> <environments default="environment"> <environment id="environment"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE"/> <property name="username" value="SCOTT" /> <property name="password" value="TIGER" /> </dataSource> </environment> </environments> <!-- 加载sql定义文件 --> <mappers> <mapper resource="cn/xdl/sql/UserMapper.xml" /> </mappers> </configuration>
-
编写实体类User.java
public class User implements Serializable{ private Integer id; private String login_name; private String nick_name; private String real_name; private Integer grade_id; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLogin_name() { return login_name; } public void setLogin_name(String login_name) { this.login_name = login_name; } public String getNick_name() { return nick_name; } public void setNick_name(String nick_name) { this.nick_name = nick_name; } public String getReal_name() { return real_name; } public void setReal_name(String real_name) { this.real_name = real_name; } public Integer getGrade_id() { return grade_id; } public void setGrade_id(Integer grade_id) { this.grade_id = grade_id; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
-
编写SQL定义文件UserMapper.xml
<?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="user"> <select id="findAll" resultType="user"> select * from xdl_user </select> <select id="findById" parameterType="int" resultType="user"> select * from xdl_user where id=#{id} </select> <delete id="delete" parameterType="int"> delete from xdl_user where id=#{id} </delete> <insert id="save" parameterType="user"> insert into xdl_user (ID,LOGIN_NAME,NICK_NAME,REAL_NAME,GRADE_ID,PASSWORD) values (#{id},#{login_name},#{nick_name},#{real_name},#{grade_id},#{password}) </insert> <update id="updateNickname" parameterType="map"> update xdl_user set nick_name=#{nick} where id=#{id} </update> </mapper>
-
获取SqlSession对象
public class MyBatisUtil { public static SqlSession openSession() throws IOException{ //SqlSessionFactoryBuilder SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //SqlSessionFactory //加载了主配置文件中的连接参数和sql定义文件信息 Reader reader = Resources.getResourceAsReader("sqlmap-config.xml"); SqlSessionFactory factory = builder.build(reader); //SqlSession SqlSession sqlSession = factory.openSession(); return sqlSession; } }
使用sqlSession调用sql语句
public class TestUser { @Test public void test1() throws IOException{ SqlSession sqlSession = MyBatisUtil.openSession(); List<User> list = sqlSession.selectList("user.findAll"); for(User user:list){ System.out.println(user.getId()+" " +user.getLogin_name()+" "+user.getNick_name()); } sqlSession.close(); } @Test public void test2() throws IOException{ SqlSession sqlSession = MyBatisUtil.openSession(); User user = new User(); user.setId(1); user.setLogin_name("tom"); user.setNick_name("tom"); user.setPassword("123"); user.setReal_name("同名"); user.setGrade_id(1); sqlSession.insert("user.save",user); sqlSession.commit(); sqlSession.close(); } @Test public void test3() throws IOException{ SqlSession sqlSession = MyBatisUtil.openSession(); int rows = sqlSession.delete("user.delete",1); System.out.println("删除记录数:"+rows); sqlSession.commit(); sqlSession.close(); } @Test public void test4() throws IOException{ SqlSession sqlSession = MyBatisUtil.openSession(); User user = sqlSession.selectOne("user.findById",37); if(user != null){ System.out.println(user.getId()+" "+user.getLogin_name()); }else{ System.out.println("未找到记录"); } sqlSession.close(); } @Test public void test5() throws IOException{ SqlSession sqlSession = MyBatisUtil.openSession(); Map<String,Object> map = new HashMap<String,Object>(); map.put("id", 37);//#{id} map.put("nick", "SPRING");//#{nick} sqlSession.update("user.updateNickname",map); sqlSession.commit(); sqlSession.close(); } }
案例4:MyBatis使用技巧
-
定义类型别名
在sqlmap-config.xml中定义< typeAliases>
<typeAliases> <typeAlias type="cn.xdl.entity.User" alias="user"/> </typeAliases>
这样在定义SQL时,parameterType或resultType属性可以指定为user,简化类型定义.
-
无效列类型:111
原因是:SQL参数使用#{}时,遇到null值。
解决方法:#{key,jdbcType=xxx}
<insert id="save" parameterType="user"> insert into xdl_user (ID,LOGIN_NAME,NICK_NAME,REAL_NAME,GRADE_ID,PASSWORD) values (#{id}, #{login_name,jdbcType=VARCHAR}, #{nick_name,jdbcType=VARCHAR}, #{real_name,jdbcType=VARCHAR}, #{grade_id,jdbcType=NUMERIC}, #{password,jdbcType=VARCHAR})
参考下表:
-
Mapper映射器
Mapper映射器是一个接口规范,按规则定义接口后,MyBatis可以自动生成接口的实现对象。(原理:动态代理技术JDK Proxy)
规则如下:
- 接口中定义方法,方法可以映射执行SQL操作
- 接口方法名与SQL定义的id保持一致
- 接口方法参数类型与SQL定义的parameterType保持一致
- 接口方法返回类型,多行查询用List;单行查询用resultType类型;增删改用int或void
- SQL定义的namespace与接口包名.类型名一致
例如SQL定义
<select id="findById" parameterType="int" resultType="user"> select * from xdl_user where id=#{id} </select>
对应的接口方法
public User findById(int id);