Lemuria 发布的文章

记一次springboot+vue+shiro的前后端分离项目中碰到的坑。


记一次springboot+vue+shiro的前后端分离项目中碰到的坑。

  1. 前言

    ​ 最近有了一个项目练手,一个简单的后台管理项目,本人学艺不精,和同学在前后端联调的时候碰上了跨域的问题,弄了一上午加半个下午才弄好,在此记录一下,希望可以帮到有需要的人。本篇文章因为作者本人技术的原因,大概率存在各种错误,仅供参考。

  2. 过程

    1. 在一个前后端分离的项目中,跨域问题是首要解决的,一开始尝试了在Controller上加上@CrossOrigin注解来解决,随后发现不起什么作用,于是去谷歌查了一下,说是可能和shiro有关,搜索到的部分资料说自定义过滤器然后重写onAccessDenied方法可以解决,试了一下发现不怎么行,当然不行的原因不在这里。
    2. 随后查询了前后端分离时,前端需要做的工作,一个普遍的答案是加上

      axios.defaults.withCredentials = true;
    3. 但还是没有解决跨域的问题,于是按照网上的方法重写了过滤器,加了一些调试代码,如下:(记得注册这个自定义的过滤器,相关代码网上有)

      public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
      
      
      
          private static final Logger logger = LoggerFactory.getLogger(MyFormAuthenticationFilter.class);
      
          public MyFormAuthenticationFilter() {
              super();
          }
      
          @Override
          public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
              HttpServletRequest req = (HttpServletRequest)request;
              String authorization = ((HttpServletRequest) request).getHeader("Authorization");
              System.out.println("===================================");
              System.out.println("authorization: " + authorization);
      
              Subject subject = SecurityUtils.getSubject();
              Serializable id = subject.getSession().getId();
              System.out.println("这是subject: "+subject.toString());
              System.out.println("这是SessionId: "+id.toString());
              System.out.println("这是登录的用户信息:" + subject.getPrincipals());
              if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                  return true;
              }
      
              return super.isAccessAllowed(request, response, mappedValue);
          }
      
          @Override
          protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
              HttpServletResponse res = (HttpServletResponse)response;
              HttpServletRequest req = (HttpServletRequest)request;
              //设置origin,此值不能为‘*’//
             
              String origin = req.getHeader("Origin");
              if(origin == null) {
                  origin = req.getHeader("Referer");
              }
              res.setHeader("Access-Control-Allow-Origin", origin);
              System.out.println(origin);
              res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
              res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,token");
              // 设置跨域//
              res.setHeader("Access-Control-Allow-Credentials","true");
              res.setStatus(HttpServletResponse.SC_OK);
              res.setCharacterEncoding("UTF-8");
              PrintWriter writer = res.getWriter();
              Map<String, Object> map= new HashMap<>();
              //打印未登录信息//
              map.put("code", 403);
              map.put("msg", "未登录");
              writer.write(JSON.toJSONString(map);
              writer.close();
              return false;
          }
      
      }
    4. 以上代码还不足以解决跨域的问题,还需要加上一段配置,如下:

      @Configuration
      public class CorsConfig {
          private CorsConfiguration buildConfig() {
              CorsConfiguration corsConfiguration = new CorsConfiguration();
              corsConfiguration.addAllowedOrigin("*");
              corsConfiguration.addAllowedHeader("*");
              corsConfiguration.addAllowedMethod("*");
              corsConfiguration.setAllowCredentials(true);
              return corsConfiguration;
          }
          @Bean
          public CorsFilter corsFilter() {
              UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
              source.registerCorsConfiguration("/**", buildConfig());
              return new CorsFilter(source);
          }
      }

      以上两段代码可能会有不需要的地方或者是没有用的代码,我现在技术太差,还不能看出到底有什么可以改进的地方,如有高见还请评论区指示。

    5. 至此,跨域的问题已经解决了,但是随之而来的是JSESSIONID丢失的问题,因为http是无状态协议,所以前端提交请求时需要附带一个JSESSIONID给后端,让后端判断用户的身份信息,但是这个JSESSIONID是存储在Cookie里面的,跨域的时候这个Cookie不会跟着一起发送,看起来就像是JSESSIONID丢失了一样。然后服务端就会认为又是一个新的用户来了(因为没有带着JSESSIONID请求),就会创建一个新的Session,表现在前端就是报未登录错误(如果你有的话)
    6. 有一个想法是后端验证登录成功后返回一个JSESSIONID给前端,然后前端写入到Cookie里面,再带着请求发送到后端,这样就可以解决JSESSIONID丢失的问题了,但是尝试后发现,就算手动在请求头里面设置了Cookie,请求的时候这个Cookie还是不会被发出去,无奈之下放弃了(可能是我知识面太窄,可能还有我不知道的其他方法)
    7. 到这里离解决方案已经很近了,我们可以不设置Cookie,在请求头里面设置一个”Authorization",它的值就是后端传给前端的JSESSIONID,然后重写DefaultWebSessionManager类,把JSESSIONID改为接收到的Authorization,这样就绕过了用Cookie来获取JSESSIONID,如下

      public class ShiroSessionManager extends DefaultWebSessionManager {
      
          private static final String AUTHORIZATION = "Authorization";
      
          private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
      
          public ShiroSessionManager(){
              super();
          }
      
          @Override
          protected Serializable getSessionId(ServletRequest request, ServletResponse response){
              String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
              System.out.println("id:"+id);
              if(StringUtils.isEmpty(id)){
                  //如果没有携带id参数则按照父类的方式在cookie进行获取
                  System.out.println("super:"+super.getSessionId(request, response));
                  return super.getSessionId(request, response);
              }else{
                  //如果请求头中有 authToken 则其值为sessionId
                  request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
                  request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
                  request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
                  return id;
              }
          }
      }

      然后注册一下就行了,如下:

      @Bean
          public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("shiroConfigRealm") Realm realm){
              DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
              defaultWebSecurityManager.setRealm(realm);
              defaultWebSecurityManager.setSessionManager(sessionManager());
              return defaultWebSecurityManager;
          }
      @Bean
          public SessionManager sessionManager(){
              ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
              //这里可以不设置。Shiro有默认的session管理。如果缓存为Redis则需改用Redis的管理
              shiroSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
              return shiroSessionManager;
          }    
  3. 结论

    到此,跨域问题和JSESSIONID丢失的问题都解决了,以上解决方案可能不能解决你的问题,但起码可以作为一个参考。


使用SpringBoot写一个一言api


使用SpringBoot写一个一言api

高中的时候很喜欢这东西,不过那时候因为技术原因写不出来,别人的代码也看不懂,今天突然想起这个,就随手写了一个。

Github: https://github.com/Aurora1468/hitokoto-project-for-Spring-Boot

首先去一言的句子库把句子下下来:

一言的句子库: [https://github.com/hitokoto-osc/sentences-bundle]

放在resource文件夹下就行:

![image-20201229215908223](E:UsersLemuriaAppDataRoamingTyporatypora-user-imagesimage-20201229215908223.png)

到这里准备工作就完成了,然后开始写controller,首先在方法外面读取文件。

ObjectMapper mapper = new ObjectMapper();
    ArrayList<ArrayList<Object>> lists = new ArrayList<>();
    {
        try {
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/a.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/b.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/c.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/d.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/e.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/f.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/g.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/h.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/i.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/j.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/k.json").getInputStream(), ArrayList.class));
            lists.add(mapper.readValue(new ClassPathResource("public/sentences/l.json").getInputStream(), ArrayList.class));
        } catch (IOException e) {
            e.printStackTrace();
        }

需要注意的是不能使用getFile(),不然打成jar包的时候会出现找不到文件路径的问题。

然后就是常规的请求:

@RequestMapping("/hitokotoApi")
    public String hitokotoApi(Param params) throws UnsupportedEncodingException {
        
        int jsonIndex;
        
        if(params.getC() == null){
            jsonIndex = (int)(Math.random() * 12);
        } else
            jsonIndex = params.getC().toCharArray()[0]-'a';
        ArrayList<Object> list = lists.get(jsonIndex);
        HashMap<String,Object> map = (HashMap) list.get((int)(Math.random() * list.size()));
        String ret = map.toString();
        if(params.getEncode()!=null&&params.getEncode().equals("text")){
            ret = (String) map.get("hitokoto");
        }
        if(params.getCharset()!=null&&params.getCharset().equals("gbk")){
            ret=new String(ret.getBytes(),"GBK");
        }
        accessNum++;
        System.out.println("被访问了:" + accessNum + "次");
        return ret;
    }

为了防止被过度使用(虽然估计也没几个人会用2333),添加了一个频率检测,如下:

//定义一个注解
@Documented
@Target(ElementType.METHOD) // 说明该注解只能放在方法上面
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRequest {
    long time() default 5000; // 限制时间 单位:毫秒
    int count() default 3; // 允许请求的次数
}
//具体实现
@Aspect
@Component
public class LimitRequestAspect {

    private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>();

    // 定义切点
    // 让所有有@LimitRequest注解的方法都执行切面方法
    @Pointcut("@annotation(limitRequest)")
    public void excudeService(LimitRequest limitRequest) {
    }

    @Around("excudeService(limitRequest)")
    public Object doAround(ProceedingJoinPoint pjp, LimitRequest limitRequest) throws Throwable {

        // 获得request对象
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        // 获取Map对象, 如果没有则返回默认值
        // 第一个参数是key, 第二个参数是默认值
        ExpiringMap<String, Integer> uc = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build());
        Integer uCount = uc.getOrDefault(request.getRemoteAddr(), 0);



        if (uCount >= limitRequest.count()) { // 超过次数,不执行目标方法
            return "接口请求超过次数";
        } else if (uCount == 0){ // 第一次请求时,设置有效时间
//            /** Expires entries based on when they were last accessed */
//            ACCESSED,
//            /** Expires entries based on when they were created */
//            CREATED;
            uc.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MILLISECONDS);
        } else { // 未超过次数, 记录加一
            uc.put(request.getRemoteAddr(), uCount + 1);
        }
        book.put(request.getRequestURI(), uc);

        // result的值就是被拦截方法的返回值
        Object result = pjp.proceed();

        return result;
    }


}

然后把这个注解添加到需要检测频率的方法上面就行了。

最后把它部署到服务器上,因为是jar包,所以一句命令就行了,然后在nginx里配置一下代理就可以不用端口访问了:

location / {

            #这里是编写监听到的请求所转发的端口号,即tomcat端口
            proxy_pass http://localhost:12345;
            #设置nginx 的默认显示页,可有可无
            index  index.html index.htm;
            #设置http请求的请求头,使其在跨域访问上不会被浏览器阻止。ps:这里设置我发现没有用,后来还是在ajax过滤器中添加的 请求头,如果大家有知道这里怎么修改的,请留言大家一起学习。
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

上面这个配置文件是百度找的,随便修改了一下。

使用

你可以使用get或者post请求来使用这个api,如:

https://api.acgnx.top/hitokotoApi
https://api.acgnx.top/hitokotoApi?encode=text

上面这个请求不附带参数的时候是完全随机的,如果附带请求的话,如下:

参数可选说明
c见后表句子类型
encode见后表返回编码
charset见后表字符集

句子类型(参数):

参数说明
a动画
b漫画
c游戏
d文学
e原创
f来自网络
g其他
h影视
i诗词
j网易云
k哲学
l抖机灵
其他作为 动画 类型处理

返回编码(参数)

参数说明
text返回纯洁文本
json返回格式化后的 JSON 文本
其他返回格式化后的 JSON 文本

字符集(参数)

参数说明
utf-8返回 utf-8 编码的内容
gbk返回 gbk 编码的内容。
其他返回 utf-8 编码的内容

Spring学习记录01


Spring学习记录

这是期末作业,本文内容基于个人理解和官方文档以及网上一些资料,如有错误烦请评论区指出

1. Spring是什么

  • Spring是一个开源免费的框架 , 容器 .
  • Spring是一个轻量级的框架 , 非侵入式的 .
  • 控制反转 IoC , 面向切面 Aop
  • 对事物的支持 , 对框架的支持

2. IoC

  • IoC,中文名字控制反转,在Spring中是一个容器,在我们以前的学习的程序中,对使用者来说,程序的控制创建都是交给程序的,但是控制反转则是由我们自行控制创建对象,把主动权交给了调用者,例如程序需要一个对象的时候,不直接在原来的类或者方法中创建,而是由调用者传入。
  • 实现IoC的方法有很多种,现在学习的Spring使用的是DI,也就是依赖注入,上文中由调用者传入一个对象指的就是依赖注入,而控制反转的指的就是获得依赖对象的方式反转了。

3. 使用Spring

  • 和使用大多数框架一样,我们首先需要导入必要的包,如果使用maven则可以直接写入配置文件:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.10.RELEASE</version>
    </dependency>
  • 编写一个实体类:

    public class Hello {
        private String name;
     
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
     
        public void show(){
            System.out.println("Hello,"+ name );
        }
    }
  • 编写Spring配置文件applicationContext.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
     
        <!--bean就是java对象 , 由Spring创建和管理-->
        <bean id="hello" class="com.kuang.pojo.Hello">
            <property name="name" value="Spring"/>
        </bean>
    </beans>
  • 编写测试类:

    @Test
    public void test(){
        //解析beans.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //getBean : 参数即为spring配置文件中bean的id .
        Hello hello = (Hello) context.getBean("hello");
        hello.show();
    }

    在上面的例子中,对象的创建从我们写的程序变成了Spring容器,这就是控制反转。在Hello类中有set方法,从而可以进行依赖注入。

4. 依赖注入和自动装配

  • 依赖注入

    • 依赖注入有配置文件注入和注解注入两种,配置文件注入中有一下几种方式:

      1. 常量注入
      2. Bean注入
      3. 数组注入
      4. List注入
      5. Map注入
      6. Set注入
      7. Null注入
      8. Properties注入
    • 这其中需要注意的是,在传入值的时候,分为value和ref两种,前者指的是值类型,后者是引用类型,在依赖注入的时候,被注入的对象必须要有set方法,其命名要符合JavaBean规范。
    • 具体注入方式参考官网,在此不表。
  • 自动装配

    在Spring中有三种装配方式,分别是:

    1. 在xml中显式配置;
    2. 在java中显式配置;
    3. 隐式的bean发现机制和自动装配

在自动化的装配中,我们可以给bean标签加上autowire属性,分别有byName和byType两种,顾名思义,前者是按名字装配,后者是按类型装配。

5. 注解

  • Spring中的注解种类繁多,使用注解可以大幅减小配置文件的数量,降低开发难度。使用注解首先要开启注解支持,还要引入aop的包,再加上context约束:

    <context:annotation-config/>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.7.RELEASE</version>
    </dependency>
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
     
    </beans>
  • 以下是几种常见的Spring注解:

    1. @Configuration 相当于配置文件中的beans,使用之前要在配置文件中开启扫描

      <context:component-scan base-package="xxx.xxx"/>
    2. @Scope 可以配置作用域
    3. @Value 可以手动给属性注入值
    4. @Controller, @Service, @Repository,@Component 相当于bean标签,在类上使用,使用前要在配置文件中开启扫描

      <context:component-scanbase-package=”XXX”/>

      以上四种注解功能相同,只是为了区分所以名字不同。使用以上四种标签时,默认id为类名首字母转小写。

    5. @Autowired 配置在属性上,实现自动装配
    6. @Bean 相当于Bean标签,在方法上使用,方法的名字就相当于bean标签中的id,其返回值相当于class属性。

6. 代理模式

  • 在学习AOP之前,先要学习代理模式,因为AOP的底层机制就是动态代理。

    代理模式分为两种,一种是动态代理,一种是静态代理。、

    在静态代理中,对象有以下四个:

    1. 抽象角色:使用接口或者抽象类来实现
    2. 真实角色:被代理的角色
    3. 代理角色:代理真实的角色,然后做一些附加操作
    4. 客户:使用代理觉得来做一些事情。

综上所述,之所以要使用代理模式是因为原来的角色(真实角色),多出了需求,但是因为某些原因不适合更改真实对象的代码时,就可以使用代理对象来代替真实对象完成工作。这样一来,有如下几点好处:

  1. 真实对象的工作是纯粹的,不用关注细枝末节
  2. 可以让业务扩展时变得集中和方便管理。
  3. 实现了业务的分工

但是相对应的,代理类越多,工作量就越大,开发效率遍低。

  • 动态代理
  • 动态代理有以下几点:

    • 动态代理的角色和静态代理的一样 .
    • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
    • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
  • 举一个例子:

    //一个用户增删改查的接口
    package top.acgnx.service;
    
    public interface UserService {
        public void add();
        public void delect();
        public void update();
        public void query();
    }
    //一个实现类作为真实对象,模拟增删改查操作
    package top.acgnx.service;
    
    public class UserServiceImpl implements UserService{
        public void add() {
            System.out.println("add User");
        }
    
        public void delect() {
            System.out.println("delect User");
        }
    
        public void update() {
            System.out.println("update User");
        }
    
        public void query() {
            System.out.println("query User");
        }
    }

    现在需要对上面的真实对象进行增强,使其具有输出日志的功能。

    import java.lang.reflect.Proxy;
    
    //代理对象
    public class ProxyInvocationHandler implements InvocationHandler {
        Object object;
    
        //传入要被代理的真实对象
        public void setObject(Object object) {
            this.object = object;
        }
    
        //使用newProxyInstance方法创建一个代理类并返回
        public Object getProxy(){
            return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
        }
    
        //执行被调用的方法,可以在这里面对原有的方法进行增强
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log(method.getName());
            //执行传入的方法
            Object ret = method.invoke(object, args);
            //返回执行的结果
            return ret;
        }
    
        public void log(String s){
            System.out.println("方法" + s + "被执行了");
        }
    }

    上面这个类实现了InvocationHandler接口,传入一个真实对象,就可以使用getProxy()方法得到一个被代理后的对象,代理对象调用真实对象中的方法的时候,就会通过invoke被执行,同时增强原有的方法,并把执行后的结果返回。

    import org.junit.Test;
    import top.acgnx.service.ProxyInvocationHandler;
    import top.acgnx.service.UserService;
    import top.acgnx.service.UserServiceImpl;
    
    public class ProxyText {
        @Test
        public void proxyTest(){
            //创建真实对象
            UserServiceImpl userService = new UserServiceImpl();
    
            //创建代理对象
            ProxyInvocationHandler pih = new ProxyInvocationHandler();
    
            //注入要代理的对象
            pih.setObject(userService);
    
            //得到被代理后的对象并强转类型
            UserService proxy = (UserService) pih.getProxy();
    
            //执行增强后的方法
            proxy.add();
        }
    }

    上述代码可以传入任意一个真实对象,并在执行原有方法前输出方法名。


MyBatis笔记


MyBatis学习记录

这是期末作业,以下内容大多数参考MyBatis官方文档(未完)
MyBatis官方文档

1. 什么是 MyBatis?

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

2. 如何使用Mybatis

  • 在使用mybatis之前,需要导入mybatis的包,常用的方式有两种:

    1、手动导入jar包

    2、使用maven配置dependency

    • pom文件的配置:

      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>x.x.x</version>
      </dependency>

      其中x.x.x为版本号

  • 配置mybatis-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>
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
          </dataSource>
        </environment>
      </environments>
      <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
      </mappers>
    </configuration>
  • 完成后需要从xml文件中构建SqlSessionFactory
    String resource = "org/mybatis/example/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    以上内容可以封装成为一个工具类。除此以外还可以不适用xml构建,在此不表,有兴趣可以翻阅官方文档

  • 有了SqlSessionFactory后我们就可以获取SqlSession。用来执行sql语句
    try (SqlSession session = sqlSessionFactory.openSession()) {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectBlog(101);
    }

    使用接口的方式可以让代码变得更清晰,更加类型安全

3. 生命周期和作用域

  • SqlSessionFactoryBuilder
    • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
  • SqlSessionFactory
    • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
  • SqlSession
    • 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
    • try (SqlSession session = sqlSessionFactory.openSession()) {
        // 你的应用逻辑代码
      }