Mybatis概述

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。mybatis 通过 xml注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回

Mybatis配置

搭建mybatis环境的几个步骤

1:创建maven工程并导入坐标

2:创建实体类和dao的接口

3:创建Mybatis的主配置文件Conifg.xml

4:创建映射配置文件UserDao.xml

创建一个maven工程,然后配置pom.xml文件。

 <packaging>jar</packaging>   //打包方式设置为jar
    <dependencies>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>

完成后,创建一个userdao的接口

package com.test.dao;

import com.test.domain.User;

import java.util.List;

public interface Userdao {
    List<User> findAll();
}

然后需要创建一个config.xml文件来指定连接数据库的参数

mapconfig.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">
<!-- mybatis的主配置文件 -->
<configuration>
    <!-- 配置环境 -->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的4个基本信息 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
    <mappers>
        <mapper resource="com/test/dao/Userdao.xml"/>
    </mappers>
</configuration>

还得添加一个映射配置文件

<?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="com.test.dao.Userdao">

    <select id="findAll" resultType="com.test.domain.User">
        select * from user
    </select>


</mapper>

这里就搭建完成了,这里还有几个注意事项:

1.创建UserDao.xml 和 UserDao.java时名称是为了和我们之前的知识保持一致。
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:UserDao 和 UserMapper是一样的

2.mybatis的映射配置文件位置必须和dao接口的包结构相同		
3.映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
4.映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

这样我们就不需要再创建dao的实现类来完成jdbc的操作了。到这一步mybatis的就简单配置好了。

Mybatis基本使用

mybatis配置完成后,建立一个test类,查询数据库里面的信息。

使用Mybatis步骤:

1.读取配置文件
2.创建SqlSessionFactory工厂
3.创建SqlSession
4.创建Dao接口的代理对象
5.执行dao中的方法
6.释放资源

代码:

package com.test;


import com.test.dao.Userdao;
import com.test.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatistest {


    public static void main(String[] args)throws Exception {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("mapconfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        Userdao userDao = session.getMapper(Userdao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user.toString());
        }
        //6.释放资源
        session.close();
        in.close();
    }

}

以上的是配置xml的写法,注解配置Mybatis如下:

我们只需要在接口处加一个select的注解就可以了

package com.test.dao;

import com.test.domain.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface Userdao {
    @Select("select * from user")
    List<User> findAll();
}

这样我们就可以删除前面创建的userdao.xml文件。

使用注解配配置,还需要修改一下mapconfig.xml的配置
原本的xml配置:

<mappers>
        <mapper resource="com/test/dao/Userdao.xml"/>
    </mappers>

改为:

<mappers>
        <mapper class="com.test.dao.Userdao"/>
    </mappers>

mapper 这里使用注解使用class指定被select注解的接口就完成了。

Mybatis增

添加的操作和查询的其实都差别不大,修改一下映射文件,然后从查询的基础上修改一下,就成了增加的功能了,具体看代码

<?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="com.test.dao.Userdao">

    <select id="findAll" resultType="com.test.domain.User">
        select * from user
    </select>
    <insert id="SaveUser" parameterType="com.test.domain.User">
    insert into user(name,address,gender,age,qq,email,username,password) values(#{name},#{address},#{gender},#{age},#{qq},#{email},#{username},#{password})
    </insert >

</mapper>

test类:

package com.test;
import com.test.dao.Userdao;
import com.test.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatistest {
        @Test
        public void selecttest()throws Exception{
//    public static void main(String[] args) {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("mapconfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        Userdao userDao = session.getMapper(Userdao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user.toString());
        }
        //6.释放资源
        session.close();
        in.close();
    }
    @Test
    public void Savetest() throws IOException {
        User user = new User();
        user.setName("nice");
        user.setAge(20);
        user.setGender("男");
        user.setAddress("gd");
        user.setQq("2548266145");
        user.setEmail("2548266145@qq.com");
        user.setUsername("nice0e3");
        user.setPassword("nize");


        InputStream in = Resources.getResourceAsStream("mapconfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        Userdao userDao = session.getMapper(Userdao.class);
        userDao.SaveUser(user); //调用SaveUser方法参数user对象
        session.commit();//提交事务
    }
}

在这里面分别有2个方法,一个是查询,一个是添加。虽然说功能完成了,但是这里可以看到前面的一些代码都是重复的,我们可以直接把他重复的代码封装起来,想要执行某一个功能的时候直接调用就可以了。

package com.test;


import com.test.dao.Userdao;
import com.test.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatistest {
    private InputStream in;
    private SqlSessionFactoryBuilder builder;
    private Userdao userDao;
    private SqlSession session;
    private SqlSessionFactory factory;
    private User user = new User();

    @Before  //被注解后在测试方法执行前会执行
    public void init() throws IOException {


        in = Resources.getResourceAsStream("mapconfig.xml");

        builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);

         session = factory.openSession();
         session.commit();//提交事务

        userDao = session.getMapper(Userdao.class);
    }

        @Test
        public void selecttest()throws Exception{
//    public static void main(String[] args) {
        //1.读取配置文件

        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user.toString());
        }
        //6.释放资源

    }


    @Test
    public void Savetest() throws IOException {

        user.setName("nice");
        user.setAge(20);
        user.setGender("男");
        user.setAddress("gd");
        user.setQq("2548266145");
        user.setEmail("2548266145@qq.com");
        user.setUsername("nice0e3");
        user.setPassword("nize");

        userDao.SaveUser(user);



    }

    public void Update(){
        

    }
    @After  //测试方法执行后执行
    public void destroy() throws IOException {
        session.close();
        in.close();
    }

}

把配置的信息放正在init方法里面,运行时候自动运行,进行赋值。

关闭资源的放到destroy方法里面,关闭的时候自动执行关闭资源。

封装完成后,我们的Savetest的方法除了设置值外,其实也就一行代码就完成了改功能。

调试好了前面的后面的就好写了,基本上就是复制粘贴。

Mybatis修改

前面的框架大体定义好后,在来写个修改的操作。

userdao接口添加updata方法:

void UpdataUser(User user);

映射文件添加updata标签:

    <update id="UpdataUser" parameterType="com.test.domain.User">
        update user set username = #{username},address=#{address},age=#{age} where id=#{id}

    </update>

基于上面的代码添加一个方法:

@Test
    public void Update(){
        user.setUsername("nnicee0e3");
        user.setAddress("dgd");
        user.setAge(18);
        user.setId(8);
        userDao.UpdataUser(user);
    }

运行后内容成功被修改。

Mybatis delete

至于删除的操作就更简单了。

userdao接口中添加一个方法:

 void deleteUser(Integer id);

映射文件添加delete标签:

    <delete id="deleteUser" parameterType="Integer">
        delete from user where id = #{id}
    </delete>

test类里面依旧复制粘贴一下代码:

@Test
    public void delete() {

        userDao.deleteUser(6);
    }

Mybatis 查询单条信息

一如既往的接口添加方法:

List<User> findone(Integer id);

一如既往的添加映射文件内容:

<select id="findone" resultType="com.test.domain.User" parameterType="int">

        select * from user where id =#{id}
    </select>

一如既往的test类里面调用方法:

    @Test
    public void findone(){
        List<User> findone = userDao.findone(1);
        System.out.println(findone.toString());
    }

Mybatis 模糊查询

接口添加方法:

  List<User> findlike(String name);

添加映射文件内容:

<select id="findlike" resultType="com.test.domain.User" parameterType="string">
        select * from user where name like  #{name}

    </select>

test类里面调用方法:

    @Test
    public void findlike(){
        List<User> ming = userDao.findlike("%xiao%");
        for (User user1 : ming) {
            System.out.println(user1);
        }

    }

安全

SQL注入的根本原因在于SQL语句的拼接构造

JDBC拼接不当造成SQL注入

JDBC有两种方式执行SQL语句,分别是是PrepareStatementStatement

  • Statement方法每次执行都需要编译
  • PrepareStatement会对SQL语句进行预编译,直接执行

预编译为什么能防注入

Java中预编译使用preparestatement,对SQL语句进行语法分析,编译和优化,其中用户传入的参数用占位符?代替。当语句真正运行时,传过来的参数只会被看成纯文本,不会重新编译,不会被当做sql指令。

之所以PreparedStatement能防止注入,是因为当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1',它也会作为一个字段的值来处理,而不会作为一个SQL指令,从根本上讲,其实就是~data OR code~的问题,确保data永远是data,不会是可执行的code,就永远的杜绝了SQL注入这种问题。

但是PreparedStatement也不一定能彻底安全。如下

String sql = "select * from goods where min_name = ?";  // 含有参数
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, "儿童"); // 参数赋值
System.out.println(st.toString()); 
//com.mysql.jdbc.JDBC4PreparedStatement@d704f0: select * from goods where min_name = '儿童'

最终的SQL语句如下:

select * from goods where min_name = '儿童'

此刻尝试注入一下,拼接一个单引号,最终SQL语句如下

select * from goods where min_name = '儿童\''

尝试另一种拼接,儿童后拼接一个%

select * from goods where min_name = '儿童%'

发现PreparedStatement竟然没有转义,百分号恰好是like查询的通配符。 正常情况下,like查询是这么写的:

select * from goods where min_name like '%儿童%'

查询min_name字段中间有”儿童”的所有记录,其中”儿童”二字是用户输入的查询条件

假如传入的参数是 st.setString(1, "%儿童%" + "%");sql语句变为:

select * from goods where min_name like '%儿童%%'

假如传入的只是一个%,执行的SQL语句如下:

select * from goods where min_name like '%儿童%%'

虽然此种SQL注入危害不大,但这种查询会耗尽系统资源,从而演化成拒绝服务攻击。

动态参数

String sql = " SELECT passwd FROM test_table1 WHERE username = ? ";

ps = conn.prepareStatement(sql);
ps.setString(1, username);  //# 通过setString()指明该参数是字符串类型
rs = ps.executeQuery();

ps.setString(1, username)会自动给值加上引号。比如假设username=“ls”,那么拼凑成的语句会是

String sql = " SELECT passwd FROM test_table1 WHERE username = 'ls' ";

order by后一般是接字段名,而字段名是不能带引号的,比如 order by username;如果带上引号成了order by 'username',那username就是一个字符串不是字段名了,这就产生了语法错误。

所以order by后不能参数化的本质是:

  • 预编译又只有自动加引号的setString()方法,没有不加引号的方法;
  • order by后接的字段名不能有引号。(至于为什么不弄个能不自动加引号的set方法那就不太懂了)

更本质的说法是:不只order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名函数名等等

Mybaits使用不当

了解完框架的使用,就需要关注安全问题了

上面已经说过,MybaitsMybatis的SQL语句可以基于注解的方式写在类方法上面,更多的是以xml的方式写到xml文件,这时候我们需要更多关注Mybaits配置xml文件。

Mybatis支持两种参数符号,一种是#,另一种是$。比如:

<select id="queryAll"  resultMap="resultMap">
  SELECT * FROM NEWS WHERE ID = #{id}
</select>
<select id="queryAll"  resultMap="resultMap">
  SELECT * FROM NEWS WHERE ID = ${id}
</select>

两种区别如下:

  • #{}:占位符号(在对数据解析时会自动添加' '),其实也就是进行了预编译
  • ${}:SQL拼接符号(替换结果,不会添加’ ‘,like和order by 后面使用,存在SQL注入问题,需手动添加过滤)

一般来说能用#{}的都用#{},首先是为性能考虑,相同的预编译SQL可以重复使用,其次${}在预编译之前已经将变量进行替换,容易产生SQL注入问题。

表名、字段名和order by后的做为变量时必须使用${},主要原因是表名、字段名和order by 后是字符串,使用SQL占位符替换字符串时会带上' ',这样会使SQL报错。

关于${}就不多说了,而#{}易产生SQL注入漏洞的情况主要分为以下三种

1、模糊查询

Select * from news where title like ‘%#{title}%’

在这种情况下使用#程序会报错,新手程序员就把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。正确写法:

select * from news where tile like concat(‘%’,#{title}, ‘%’)

2、in之后的多个参数

n之后多个id查询时使用# 同样会报错,

Select * from news where id in (#{ids})

正确用法为使用foreach,而不是将#替换为$

id in
<foreach collection="ids" item="item" open="("separatosr="," close=")">
#{ids} 
</foreach>

3.order by之后的参数

上边的动态参数已经说过了

小结

做代码审计时候可以利用IDEA的搜索快速定位到xml文件,看使用是否正确,后边再写一下Hibernate 框架

在用Hibernate作为查询中间层的时候,Hibernate引擎会对HQL进行解析,翻译成SQL,再交给数据库执行。

所以,HQL查询并不直接发送给数据库,而是由Hibernate引擎对查询进行解析并解释。
这也就导致了,有两种错误消息的来源,一种来自Hibernate引擎,一种来自数据库。

HQL注入相比较于常规的SQL注入而言,十分特殊。
它的特殊性在于,它没有延时函数,没有系统函数,更没有元数据表等。

很多地方说HQL不支持UNION,其实是错误的。Hibernate支持UNION的。
但是,想要使用UNION,必须在模型的关系明确后,这种情况比较少见,所以会导致UNION失败。

所以,想要利用HQL注入,是一件比较极限和小众的事情。

原文地址:http://www.cnblogs.com/gk0d/p/16865551.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性