Mybatis

Mybatis

1JDBC

JDBC 的开发步骤

1、注册驱动:主要告诉 JVM 我们的程序将要使用哪一种数据库

2、获取连接:使用 JDBC 中的类,获得数据库的连接对象 Connection

3、获得语句执行平台:通过 Connection 可以获取执行者对象,Statement、PreparedStatement.

4、执行 SQL 语句:使用执行者对象,向数据库中执行 SQL 语句,然后可以得到对应的接口,有单个结果,也可能有结果集 ResultSet。

5、处理结果

6、释放对象:关闭顺序:rs -> stmt 、ptmt -> conn

JDBC预处理对象prepareStatement概述

SQL注入问题

public class StatementMyCode {
    public static void main(String[] args) throws SQLException {
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username =sc.nextLine();
        System.out.println("请输入密码:");
        String password=sc.nextLine();
//获取JDBCUtils连接
        Connection con=JDBCUtils1.getConnection();
        //Connection con= JDBCUtils1.getConnection();
//获取Statedment对象
        Statement stat=con.createStatement();
//执行SQL语句
        String sql = "select * from users where username='"+username+"' and password ='"+password+"'";
        System.out.println(sql);
        ResultSet rs=stat.executeQuery(sql);
        if(rs.next()){
            System.out.println("登录成功!");
        }else{
            System.out.println("登录失败!");
        }
        JDBCUtils1.close(rs,stat,con);
    }
}

SQL注入出现的登录BUG

SQL注入出现的登录BUG

SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。 假设有登录案例SQL语句如下: SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码; 此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’ OR ‘a’=’a时,则真正执行的代码变为: SELECT * FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’; 此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。 为此,我们使用PreparedStatement来解决对应的问题。

原理介绍

preparedStatement:预编译对象,是Statement对象的子类。 特点:

  • 性能高

  • 会把sql语句先编译

  • 能过滤掉用户输入的关键字。 PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。

用户登录界面代码演示 (此处省略了JDBCUtils工具类)

public class prepareStatementMyCode {
    public static void main(String[] args) throws SQLException {
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username =sc.nextLine();
        System.out.println("请输入密码:");
        String password=sc.nextLine();
       //获取JDBCUtils连接
        Connection con=JDBCUtils1.getConnection();
        //Connection con= JDBCUtils.getConnection();
     //获取Statedment对象
        Statement stat=con.createStatement();
    //执行SQL语句
        String sql = "select * from users where username=? and password =?";
        PreparedStatement ps = con.prepareStatement(sql);
        ps.setObject(1,username);
        ps.setObject(2,password);
        System.out.println(sql);
        ResultSet rs=ps.executeQuery();
        if(rs.next()){
            System.out.println("登录成功!");
        }else{
            System.out.println("登录失败!");
        }
        JDBCUtils1.close(rs,stat,con);
    }
}

正确执行代码演示结果

正确执行代码演示

SQL注入代码演示

SQL注入代码演示

JDBC 的简单使用

1、

@Test
public void fun1() throws Exception {
    // 1.注册驱动
    // 这里,其实是用到了反射的原理,通过给定类的名字,让程序自动去找对应的类
    // 然后执行得到对应实例
    /*
        Class.forName("com.mysql.jdbc.Driver"):
        其中一个原因是运行期以反射的方式来检查JDBC驱动的主类com.mysql.jdbc.Driver是否存在,这样的话当     我们知道了jar包不存在的时候我们就可以做更多的其他操作了
        另外一个原因是:jdbc是Java的规范,就是为JDk提供了好多的接口,但是没有任何的实现,与之对应的自然    是java.sql.*,你的代码只是知道java.sql.*而并不知道com.mysql.*,com.oracle.*.我们现在用的         com.mysql.jdbc.Driver也是其中的东西,如果你想用原来的方式导入类的话(就是直接在上面写import         com.mysql.jdbc.Driver)就违背了开闭原则(对拓展开放,对修改关闭)源代码里面有static块
​
    */
    Class.forName("com.mysql.jdbc.Driver");
 
    // 使用注册方法的时候,底层会注册两次,不推荐使用
    // DriverManager.registerDriver(new com.mysql.jdbc.Driver());
 
    // 2.获取连接:数据库地址、用户名、密码 Connection
    String url = "jdbc:mysql://localhost:3306/test";
    String user = "root";
    String password = "root";
    Connection conn = DriverManager.getConnection(url, user, password);
 
    // 3.获取执行者对象
    Statement stmt = conn.createStatement();
 
    // 4.准备 SQL 语句
    String sql = "select * from product";
 
    // 5.执行 SQL 语句
    ResultSet rs = stmt.executeQuery(sql);
 
    // 6.处理结果
    while (rs.next()) {
        int idStr = rs.getInt("id");
        String nameStr = rs.getString("name");
        double priceStr = rs.getDouble("price");
        String markStr = rs.getString("mark");
        System.out.println(idStr + "--" + nameStr + "--" + markStr);
    }
 
    // 7.关闭资源
    rs.close();
    stmt.close();
    conn.close();
}

2、

Public static void main(String[] args) {
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
​
            try {
                //加载数据库驱动
                Class.forName("com.mysql.jdbc.Driver");
​
                //通过驱动管理类获取数据库链接
                connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mysql");
                //定义sql语句 ?表示占位符
            String sql = "select * from user where username = ?";
                //获取预处理statement
                preparedStatement = connection.prepareStatement(sql);
                //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
                preparedStatement.setString(1, "王五");
                //向数据库发出sql执行查询,查询出结果集
                resultSet =  preparedStatement.executeQuery();
                //遍历查询结果集
                while(resultSet.next()){
                    System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //释放资源
                if(resultSet!=null){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(preparedStatement!=null){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(connection!=null){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
​
            }
​
        }

上面代码有如下几个问题:

  • 数据库连接,使用时创建,不使用就关闭,对数据库进行频繁连接开启和关闭,造成数据库资源的浪费

    • 解决:使用数据库连接池管理数据库连接

  • 将sql 语句硬编码到Java代码中,如果sql语句修改,需要对java代码重新编译,不利于系统维护

    • 解决:将sql语句设置在xml配置文件中,即使sql变化,也无需重新编译

  • 向preparedStatement中设置参数,对占位符位置和设置参数值,硬编码到Java文件中,不利于系统维护

    • 解决:将sql语句及占位符,参数全部配置在xml文件中

  • 从resutSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。

    • 解决:将查询的结果集,自动映射成java对象

2、Mybatis框架原理

什么是Mybatis

  • mybatis是一个持久层的框架,是apache下的顶级项目。

    数据持久化

    • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程

    • 内存:断电即失

    • 数据库(Jdbc),io文件持久化

    • 生活方面例子:冷藏,罐头。

    • 为什么需要持久化?

      • 不想丢掉一些对象

      • 内存太贵

  • mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。

  • mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)

Mybatis原理图: *

这里写图片描述

Mybatis中会将Mapper接口映射Mapper.xml,我们在使用Mybatis时,无需创建Mapper接口具体的实现类,而是利用JDK动态代理,动态的生成接口的实现。 *img

3、Mybatis中如何解析所有配置的Mapper映射文件

在mybatis-config.xml中,<mappers>元素的配置方式有以下几种:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
​
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
​
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
​
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
​

在<mappers>元素中,有两类子元素<mapper>和<package>,其中<mapper>子元素又有三种属性可以加载对应的配置文件,分别是:resource、url和class。在这几种配置方法中,底层最终都是通过两种方式实现,一种是面向接口编程的方式:通过MapperRegisty的addMapper()方法实现了映射关系的解析和注册,另外一种是解析普通映射配置文件的方式 <mappers>元素解析入口

在XMLConfigBuilder类下的mapperElement方法:

 

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {//解析<package>元素
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);//通过addMapper()方法解析
        } else {//解析<mapper>元素
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
              //判断resource是否有值
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();//通过parse()方法解析
          } else if (resource == null && url != null && mapperClass == null) {
              //判断url是否有值
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();//通过parse()方法解析
          } else if (resource == null && url == null && mapperClass != null) {
              //判断mapperClass是否有值
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);//通过addMapper()方法解析
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

1、解析<package>元素 该类型的解析,首先是通过属性name获取表示的包名,然后通过反射机制,加载包下所有符合条件的类,然后在通过MapperRegistyaddMapper()方法实现了映射关系的解析和注册。

2、解析属性为resource或url的<mapper>元素 使用<mapper>子元素且使用的属性是resource或url时,他们的解析方式基本上是一样。首先通过资源路径加载资源,然后构建XMLMapperBuilder实例对象,再通过XMLMapperBuilder实例对象的parse()方法来完成解析和注册。

3、解析属性为class的<mapper>元素 使用<mapper>子元素且使用的属性是class时,这种方法和解析<package>元素方法类似,区划就是:一个解析包下的多个类,一个只解析一个类而已,所以仅前置处理不一样。直接通过configuration.addMapper()方法进行解析,而该方法其实就是直接调用了MapperRegistyaddMapper()方法实现了映射关系的解析和注册。

=========================================================================

mapperParser.parse()-->XMLMapperBuilder类下的parse()方法

 

configuration.addMapper()-->Configuration类下的addMapper()方法

 


Configuration类下的addMapper()方法-->MapperRegistry下的addMapper方法

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值