记录自己在狂神说java中的学习情况,文章里有自己学习的理解和扩展,新手难免有理解偏差或者错误,恳请大佬指正。
Java自学 MyBatis 进阶
继承上一次学习的代码。
1.万能的map
如果类的种类特别多,或者数据库中的表、字段或者参数过多,类型也特别复杂,那么可以把类型的确定交给真正调用的端确认。通过map可以随意写参数,想怎么定制怎么定制。
在UserMapper接口里新创一个方法:
//使用map来增加用户
boolean insertUserUseMap(Map<String, Object> map);
//使用map根据ID查询用户
User getUserByIdUseMap(Map<String,Object> map);
在对应的mapper.xml文件里用万能的map写参数
<!--使用map后,这里的userId,userName,password可以自己定义名称-->
<insert id="insertUserUseMap" parameterType="map">
insert into mybatis.user (id,name,pwd) values (#{userId},#{userName},#{password})
</insert>
<select id="getUserByIdUseMap" parameterType="map" resultType="com.hj.pojo.User">
select * from mybatis.user where id=#{id} and name=#{name};
</select>
然后在测试文件里写测试代码:
@Test
public void insertUserUseMap() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userId", 5); //这里的键名要和mapper.xml里一致
map.put("userName", "李飞");
map.put("password", "485615");
boolean flag = mapper.insertUserUseMap(map);
if (flag) System.out.println("插入成功");
sqlSession.commit();
sqlSession.close();
}
@Test
public void getUserByIdUseMap() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("id", 3);
map.put("name", "王五");
User user =mapper.getUserByIdUseMap(map);
System.out.println(user);
sqlSession.close();
}
小结:
Map传递参数,直接在sql中取出key即可!【parameterType=“map”】
对象传递参数,直接在sql中取出对象的属性即可!【parameterType=“Object”】
只有一个基本类型参数的情况下,可以直接在sql中取到!多个参数用Map,或者注解!
思考题:模糊查询怎么写?
就是sql里的like语句。在java代码执行的时候传递通配符%%或者在sql拼接中使用通配符%%
接口中:
//模糊查询
List<User> getUserLike(String value);
接口对应的mapper.xml文件中:
这是要在java代码执行的时候传递通配符
<select id="getUserLike" resultType="com.hj.pojo.User">
select * from mybatis.user where name like #{value};
</select>
对应的测试代码
@Test
public void getUserLike(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList =mapper.getUserLike("%李%");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
这是在sql拼接中使用通配符
<select id="getUserLike" resultType="com.hj.pojo.User">
select * from mybatis.user where name like "%"#{value}"%";
</select>
对应的测试代码
@Test
public void getUserLike(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList =mapper.getUserLike("李");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
再思考:Mybatis是怎么防止sql注入的?
什么是sql注入?sql注入是一种代码注入技术,用于攻击数据驱动的应用,恶意的SQL语句被插入到执行的实体字段中。攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“or ‘1’=’1’”这样的语句),有可能入侵参数检验不足的应用程序。所以,在我们的应用中需要做一些工作,来防备这样的攻击方式。在一些安全性要求很高的应用中(比如银行软件),经常使用将SQL语句全部替换为存储过程这样的方式,来防止SQL注入。这当然是一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式。
那么mybatis是怎么防止sql注入的呢?
这里还要提到mybatis中**#{}和${}的区别**
-
#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
如:where username=#{username},如果传入的值是111,那么解析成sql时的值为where username=“111”, 如果传入的值是id,则解析成的sql为where username=“id”。如果传入的值是**;drop table user;,则解析成的sql为:select id, username, password, role from user where username=“;drop table user;“,很显然表里不会有name为“;drop table user;”**的记录,所以不会返回任何查询结果。 -
$将传入的数据直接显示生成在sql中。
如:where username=${username},如果传入的值是111,那么解析成sql时的值为where username=111;如果传入的值是;drop table user;,则解析成的sql为:**select id, username, password, role from user where username=;drop table user;**这就是被注入了,删除user表。
-
#方式能够很大程度防止sql注入,$方式无法防止Sql注入。因为#会自动把我们传入的值在外面加入“”号。
-
$方式一般用于传入数据库对象,例如传入表名。
-
一般能用#的就别用 , 若 不 得 不 使 用 “ ,若不得不使用“ ,若不得不使用“{xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。
-
在MyBatis中,“ x x x ” 这 样 格 式 的 参 数 会 直 接 参 与 S Q L 编 译 , 从 而 不 能 避 免 注 入 攻 击 。 但 涉 及 到 动 态 表 名 和 列 名 时 , 只 能 使 用 “ {xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“ xxx”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“{xxx}”这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。
总结:在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。
MyBatis的SQL是一个具有“输入+输出”的功能,类似于函数的结构。在mapper标签下,parameterType表示了输入的参数类型,resultType表示了输出的参数类型。这首先限制了我们输入输出的参数类型,无法肆意妄为。另外使用#的即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:
select id, username, password, role from user where username=? and password=?
不管输入什么参数,打印出的SQL都是这样的。这是因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。
原理:MyBatis是如何做到SQL预编译的呢?其实在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。简单说,#{}是经过预编译的(相当于JDBC中的PreparedStatement),是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。
本小节代码
https://github.com/IPostYellow/mybatisStudyRecords
中的mybatis-03
2.配置解析
官方文档中有XML配置详细说明:https://mybatis.org/mybatis-3/zh/configuration.html
这里只做一些常见内容的简单描述,完整详情可以参考官方文档。
对于核心配置文件
就是之前用过的mybatis-config.xml。Mybatis的配置文件包含了会影响Mybatis行为的设置和属性信息,从官方文档中可以看到以下信息:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
对于配置环境(environments)
MyBatis可以配置成适应多种环境,但是尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。MyBaits默认的事务管理器是JDBC(总共可以有JDBC、MANAGED两种选择),数据源是连接池POOLED(总共有UNPOOLED、POOLED、JNDI三种选择)。
具体的详细内容可以看到 https://mybatis.org/mybatis-3/zh/configuration.html 中的environments部分。
这些都是死的,当你需要用到某一种的时候,去官方文档查询即可。
对于属性(properties)
我们可以通过properties属性来实现引用配置文件,这些属性都是可外部配置且可动态替换的,既可以在典型的java属性文件中配置,也可通过properties元素的子元素来传递。
比如,第一步。在resources文件夹下新建一个db.properties,内容如下:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=root
第二步,在核心配置文件中引入properties文件。
<!--引入外部配置文件-->
<properties resource="db.properties">
<property name="user" value="root"/>
<property name="password" value="123456"/>
</properties>
这里既可以用外部的db.properties文件里的变量,也可以用自己<property>标签里的变量,如果变量name相同,则默认先用外部文件的变量。
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--mysql 8.x 是com.mysql.cj.jdbc.Driver-->
<property name="driver" value="${driver}"/>
<!--连接数据库的时候有个url,然后具体到mybatis这个数据库,然后使用ssl,编码utf8等等设置-->
<property name="url"
value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
另外注意在核心配置文件中,这些标签是有先后顺序之分的,必须按照这个顺序写,不然会报错。
- 可以直接引入外部文件db.properties
- 也可以自己新增属性
- 如果外部引入和自己新增属性的名字相同,默认用外部引入的
对于类型别名(typeAliases)
类型别名是为Java类型设置一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
比如,我们原本的mapper.xml文件里,resultType使用了我们的User类,要使用完全限定名com.hj.pojo.User
<select id="getUserList" resultType="com.hj.pojo.User">
select * from mybatis.user;
</select>
现在可以使用typeAliases来进行别名修改。
<!--可以给实体类起别名-->
<typeAliases>
<!--自定义别名-->
<typeAlias type="com.hj.pojo.User" alias="User"/>
<!--扫描这个包,默认别名为类的小写名(但是大写也可以)-->
<package name="com.hj.pojo"/>
</typeAliases>
那么对应的mapper.xml里的内容可以修改为:
<!--对应自定义别名-->
<select id="getUserList" resultType="User">
select * from mybatis.user;
</select>
或者
<!--对应扫描包-->
<select id="getUserList" resultType="user">
select * from mybatis.user;
</select>
在实体类比较少的时候,可以用自定义别名,如果实体类比较多,则可以使用第二种。
第一种方式本身可以自己随意起别名,第二种方式本身只能取类名作为别名。(但是可以通过注解再自定义别名)
对于设置(settings)
这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为。完整的详情可以参考官方文档。
下面仅列出其中几个比较常用的。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true/false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true/false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J|LOG4J|LOG4J2|JDK_LOGGING |COMMONS_LOGGING |STDOUT_LOGGING|NO_LOGGING | 未设置 |
对于映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件
注意:使用class或者包扫描来绑定的时候,是有要求的:
- 接口和他的mapper配置文件必须同名
- 接口和他的mapper配置文件必须在同一个包下
其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core(逆向工程,自动生成mapper、dao、entity)
- mybatis-plus(加速用的)
- 通用mapper
本小节代码
https://github.com/IPostYellow/mybatisStudyRecords
中的mybatis-04