Mybatis
一、第一个Mybatis程序
思路:搭建环境–>导入Mybatis–>编写代码–>测试!
1.1、搭建环境
1.1.1、搭建数据库
1.1 .2、新建项目
- 新建一个普通Maven项目
- 删除src目录,这样可以当一个父工程
- 添加依赖,Mysql,MyBtatis,junit
<!--父工程 -->
<groupId>com.yubao</groupId>
<artifactId>Mybatis-Study</artifactId>
<version>1.0-SNAPSHOT</version>
<!--依赖-->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
1.2、创建模块
在父项目里面已经导好依赖,这样子模块就不用再次添加依赖
1.2.1、配置Mybatis的核心配置文件
我的mysql是8.0版本以上的,在driver那里需要加cj "& amp;"就是&的转义。
<?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核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="******"/>
</dataSource>
</environment>
</environments>
</configuration>
1.2.2、编写工具类
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
也可理解为工厂模式;
如果说每次操作都要去读取一下配置文件;是比较麻烦的,那么就写个工具类把要使用的的功能封装起来吧;每次操作去调用工具类;
在utils目录下创建工具类MybatisUtils
package com.yubao.utils;
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;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory=null;
//在调用工具类时就执行;
static {
try {
//获取SqlSessionFactory对象;
//获得Mybatis配置文件;
String resource = "mybatis-config.xml";
//使用流读取资源;
InputStream inputStream = Resources.getResourceAsStream(resource);
//加载资源流;
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//从 SqlSessionFactory 中获取 SqlSession;
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
1.2.3、编写实体类
在pojo下面编写一个实体类,在这里我使用了lombok,只需要在插件里面下载,重启,然后在pom文件中添加依赖即可
只需要添加一个@Data就能省去get set的代码
package com.yubao.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String password;
public User(){
}
public User(int id,String name ,String password){
this.id=id;
this.name=name;
this.password=password;
}
}
1.2.4、编写Dao持久层
这里用的接口对应配置文件的方式;
先用UserDao命名吧;这个和之后要用的Mapper是一样的
package com.yubao.dao;
import com.yubao.pojo.User;
import java.util.List;
public interface UserDao {
List<User> findAlluser();
}
需要注意的是,现在不用去写实现类去实现接口的方式,那样子比较麻烦;
比说说写个查询方法把;要获取结果集就得需要一个一个去写setXXX,getXXX对应属性;而且每次写一个方法,就得写对应的;
好了,接着回到这个简单的项目;创建个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的空间对应创建的持久层接口;-->
<mapper namespace="com.yubao.dao.UserDao">
<!--比如说,现在要写个查询语句,id就对应接口的方法;-->
<!--resultType: 查询的结果类型-->
<select id="findAllUser" resultType="com.yubao.pojo.User">
select * from mybatis.user;
</select>
</mapper>
这时候就可以直接去测试了
1.3、测试
我们使用junit来进行测试
package com.yubao.dao;
import com.yubao.pojo.User;
import com.yubao.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
//第一步:获取SqlSession对象
SqlSession sqlSession =MybatisUtils.getSqlSession();
//方式一:getMapper
UserDao userDao= sqlSession.getMapper(UserDao.class);
List<User> userList=userDao.findAllUser();
for(User user:userList){
System.out.println(user);
}
sqlSession.close();
}
}
- 如果我们直接运行,他会报错,因为我们没有将这个UserMapper.xml文件去注册,他找不到我们这个文件,去Mybatis的配置文件里给我们之前写的文件进行注册。
<?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核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="*"/>
</dataSource>
</environment>
</environments>
<!--每个mapper都需要在核心配置文件中注册-->
<mappers>
<mapper resource="com/yubao/dao/UserMapper.xml"/>
</mappers>
</configuration>
- 注册好之后我们运行仍会报错,这是因为我们在之前学Maven的时候提到过,因为过滤的问题他不会去java目录下寻找我们的配置文件,我们需要自己去设置。只需要在父项目的pom文件里添加这段代码,仍然报错的话需要去子模块里添加
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
1.4、CRUD操作
- 注意事项:
namespace中的包名要和Dao/mapper接口的包名一致。
1.4.1、select
选择、查询语句:
- id:就是对应的namespace中的方法名
- resultType:Sql语句执行的返回类型
- parameterType:Sql语句中传入参数的类型
之前演示了查询的操作,这里就试试增删改,这三个操作是要开启事物的
根据id查询用户:
//UserMapper接口部分
User getUserById(int id);
//UserMapper xml文件部分
<select id="getUserById" parameterType="int"resultType="com.yubao.pojo.User">
select * from mybatis.user where id=#{id};
</select>
@Test
public void testSelectId(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=userMapper.getUserById(1);
System.out.println(user);
}
1.4.2、增、删、改用户
<!--添加用户 -->
<insert id="addUser" parameterType="com.yubao.pojo.User">
insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
<update id="updateUser" parameterType="com.yubao.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
@Test
public void testAdd(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper userDao=sqlSession.getMapper(UserMapper.class);
int i=userDao.addUser(new User(4,"大王","122121"));
if(i>0){
System.out.println("添加成功!!!");
}
//提交事物
sqlSession.commit();
//关闭sqlSession
sqlSession.close();
}
@Test
public void testDeleteUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper userMapper =sqlSession.getMapper(UserMapper.class);
if(userMapper.deleteUser(3)>1){
System.out.println("删除成功!");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser(){
SqlSession sqlSession =MybatisUtils.getSqlSession();
UserMapper userMapper =sqlSession.getMapper(UserMapper.class);
if(userMapper.updateUser(new User(2,"TEst","1211"))>1){
System.out.println("修改成功!!");
}
sqlSession.commit();
sqlSession.close();
}
1.5、万能的Map
当我们查询的时候,需要传递参数,有时候参数比较多,或者我们update的时候,我们可能不用一整个类,只用这个类的几个字段,这时候就可以用到Map
int updateUser2(Map<String , Object> map);
<update id="updateUser2" parameterType="map">
update mybatis.user set name=#{username} where id=#{id};
</update>
@Test
public void updateUser2(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
Map<String,Object> map=new HashMap<String ,Object>();
map.put("username","MapTest");
map.put("id",2);
if(userMapper.updateUser2(map)>1){
System.out.println("修改成功!!");
}
sqlSession.commit();
sqlSession.close();
}
-
Map 传递参数,直接在sql中取出key即可! parameterType=“map”
-
对象传递参数,直接在sql中取对象的属性即可!parameterType=“Object”
-
只有一个基本类型参数的情况下,可以直接在sql中取到
-
多个参数用Map,或者注解
1.6、模糊查询
-
Java代码在执行的时候,传递通配符% %
List<User> userList =mapper.getUserLike("%李%");
-
在sql拼接时使用通配符
select * from mybatis.user where name like "%#{value}%"
1.7、Sql注入问题
在学习Mysql的时候我们接触到了Sql注入问题,我们在使用Statement的时候用的是拼接字符串
String sql = "delete from table1 where id="+id;
如果有人将id的内容改为“3 or 1=1”。那么表中的任何记录都将被删除,后果十分严重。
所以我们学习使用了PreStatement
string sql = "delete from table1 where id=?"//构建sql语句,以?作为占位符,这个位置的值待设置
PreparedStatement pst = con.preparedStatement(sql); //创建PreparedStatement时就传入sql语句,实现了预编译
pst.setString(1,"3");
那么为什么PreparedStateement能防止Sql注入,或者说预编译为什么能防止Sql注入?
在使用Statement的时候是不进行预编译也就是我们将有sql注入拼接好的字符串拿去编译,那么它就会编译出Sql注入想要的效果(这时候 or这类的语法功能会被编译出来),然后拿去执行。不要将Sql语句简单的看为执行这一个操作,编译会将语句编译成电脑看得懂的,也就是Sql注入发生在编译阶段,然后电脑再去执行。
使用PreparedStatement时,会进行预编译,那么这时候编译出来的就是我们想要的效果,我们把参数传进去然后就会拿去执行,含有or的这一串被限定为字符串或其他的数据类型,使参数里有敏感字符如 or '1=1’也会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令,因为缺少编译这一阶段,不再具有语法功能。
Sql发送到服务器大致会有如下流程:
- 解析阶段
- 编译阶段
- 优化阶段
- 缓存阶段
- 执行阶段
PrepareStatement发送到服务器后会经历上述1、2、3、4过程,PrepareStatement并不是完整的sql语句,在执行之前还需要进行用户数据替换。
在填入用户数据时,PrepareStatement已经经历了上述过程,就不会重新编译,用户的数据只能作为数据进行填充,而不是sql的一部分。服务器从缓存中获得已经编译优化后的语句,替换掉用户数据执行,避免了sql注入。
引用自:How prepared statement prevents SQL Injection?
1、#{} 是预编译处理,mybatis在处理时,会将?替换为输入参数的值,然后调用PrepareStatement的set方法来赋值,传入字符串时,会在字符串的两端加上单引号,使用占位符的方式提高效率,防止SQL注入。
2、
:
表
示
S
Q
L
拼
接
,
将
接
收
到
的
参
数
内
容
不
加
任
何
修
饰
的
拼
接
到
S
Q
L
中
,
可
能
引
起
S
Q
L
注
入
。
{}:表示SQL拼接,将接收到的参数内容不加任何修饰的拼接到SQL中,可能引起SQL注入。
:表示SQL拼接,将接收到的参数内容不加任何修饰的拼接到SQL中,可能引起SQL注入。{}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
现在我们来看两种模糊查询的方式,为什么第一种能防止Sql注入能?
因为我们在使用第一种方式的时候,在传入参数之前就进行了预编译
二、配置解析
2.1、核心配置文件
一般命名为 mybatis-config.xml
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
2.2、 environments(环境配置)
- Mybatis可以配置成适应多种环境
- 尽管可以配置多个环境,但是每个SqlSession实例只能选择一种环境。
- 学会配置多套运行环境
- Mybatis默认的事务管理器就是JDBC, 连接池 POOLED
2.3、 properties(属性)
之前我们JDBC用的db.properties 这个配置文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username=root
password=123456
在核心配置文件中映入
<!--引入外部配置文件-->
<properties resource="db.properties">
<!--可以在其中增加一些属性配置-->
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
- db.properties放在resource目录下,可以不用写路径
- 可以直接引入外部文件
- 可以在其中增加一些属性配置
- 优先级问题:如果两个文件有同一个字段,优先使用外部配置文件的!
2.4、typeAliases(类型别名)
类型别名是为Java类型设置的一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
<select id="findAllUser" resultType="com.yubao.pojo.User">
select * from mybatis.user;
</select>
这里面的“com.yubao.pojo.User”这个名字很长,我们可以给它起个别名。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sb9e6y84-1647934165063)(/Users/sleeping/Library/Application Support/typora-user-images/image-20211028095928994.png)]
在配置问题件添加好别名之后就可以在Mapper里面使用。上面的代码就可以改为
<select id="findAllUser" resultType="com.yubao.pojo.User">
select * from mybatis.user;
</select>
也可以指定一个包名,MyBatis会在包名下面搜索需要的JavaBean
<typeAliases>
<package name="com.yubao.pojo"/>
</typeAliases>
他会扫描包下的每一个类,并用类名的首字母小写作为它的别名,我们可以直接使用。若有注解,则别名为其注解值。
@Alias ("DearUser")
public class User {
...
}
在类比较少的情况下,使用第一种起别名的方法
在类比较多的情况下,使用第二种起别名的方法
2.5、setting(设置)
2.6、其他配置
-
typeHandlers(类型处理器) objectFactory(对象工厂)
-
plugins(插件) - mybatis-generator-core(自动写mybatis代码) - mybatis-plus (更加简化的mybatis) - 通用mapper
2.7、mappers(映射器)
第一种映射方式:
xml文件可以放在任何位置只要能够找到就行。
第二种方式:使用class文件绑定注册
<mappers>
<mapper class="com.yubao.dao.UserMapper"></mapper>
</mappers>
- 接口和XML文件必须同名
- 接口文件和XML文件必须放在同一文件夹
第三种方式: 使用扫描包绑定
<mappers>
<package name="com.yubao.dao"/>
</mappers>
- 接口和XML文件必须同名
- 接口文件和XML文件必须放在同一文件夹
2.8、生命周期和作用域
生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder:
- 一旦创建了SqlSessionFactory,就不再需要它了
- 所以使用局部变量
SqlSessionFactory:
- 说白了就是类似数据库连接池
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession:
- 连接到连接池的一个请求
- 需要开启跟关闭
- SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
- 用完之后需要赶紧关闭,否则资源被占用
每一个mapper就对应一个业务!
三、解决数据库字段和类属姓名不一致问题
3.1、问题
可以看到数据库里面的是pwd,而实体类里面的是password,下面是我们的sql语句
<select id="findAllUser" resultType="com.yubao.pojo.User">
select * from mybatis.user;
</select>
完整的其实是这样的:
select id,name,pwd from mybatis.user;
那么从这里就可以看出来为什么我查出来的password为null。由此我们也可以想出一个解决方法就是起别名。
3.2、解决方法:
1:起别名
<select id="findAllUser" resultType="com.yubao.pojo.User">
select id,name,pwd as password from mybatis.user;
</select>
2:resultMap 简单使用
<resultMap id="userMap" type="User">
<result column="pwd" property="password"></result>
</resultMap>
<select id="findAllUser" resultMap="userMap">
select * from mybatis.user;
</select>
column 对应的就是数据库里面的字段,property就是对应类的属性,id对应下面sql语句的resultMap,type对应类,这里User是在配置文件里面取的别名
- resultMap元素是Mybatis中最重要最强大的元素
- ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了
- ResultMap最优秀的地方在于,虽然你已经对他相当了解了,但是更本就不需要显式地用到他们
- 如果世界总是这么简单就好了,接下来会有更复杂的。
3.2、解决方法:
1:起别名
<select id="findAllUser" resultType="com.yubao.pojo.User"> select id,name,pwd as password from mybatis.user;</select>
2:resultMap 简单使用
<resultMap id="userMap" type="User"> <result column="pwd" property="password"></result> </resultMap> <select id="findAllUser" resultMap="userMap"> select * from mybatis.user; </select>
column 对应的就是数据库里面的字段,property就是对应类的属性,id对应下面sql语句的resultMap,type对应类,这里User是在配置文件里面取的别名
- resultMap元素是Mybatis中最重要最强大的元素
- ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了
- ResultMap最优秀的地方在于,虽然你已经对他相当了解了,但是更本就不需要显式地用到他们
- 如果世界总是这么简单就好了,接下来会有更复杂的。
3:自动驼峰命名
数据库字段和实体类字段有对应关系,这里的对应关系就是数据库字段全为大写字母且单词之间用_分隔,实体类的属性名采用小驼峰式命名,一定要保证对应,例如数据库中的USER_ID对应实体类中的·userId字段。
类似于如下:
这种不对应的情况,Mybatis提供了一个自动驼峰命名规则的设置,但是默认是关闭的,所以当我们没有设置的时候,这样也是对应不上的。我们就需要在Mybatis的配置文件中添加如下配置:
<settings>
<!-- 开启自动驼峰命名规则映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
这里需要注意的就是settings标签的位置了,需要按照以下顺序排列
四、日志
4.1、日志工厂
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
4.2、Log4j
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、
- 我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
我们现在pom文件里面添加依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
上面提到我们可以通过一个配置文件来灵活的进行配置
我们在resoures里面添加一个log4j.properties文件,下面是我从网上找来的一个配置文件
### 设置日志级别
log4j.rootLogger=info,stdout,D,R
### 输出到控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c:%L]-[%p] %m%n
### 输出到日志文件
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
## 异常日志文件名
log4j.appender.D.File=.log/yubao.log
log4j.appender.D.DatePattern='.'yyyy-MM-dd
log4j.appender.D.Append = true
## 输出INFO级别以上的日志
log4j.appender.D.Threshold=INFO
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c:%L]-[%p] %m%n
### 保存异常信息到单独文件
log4j.appender.R = org.apache.log4j.DailyRollingFileAppender
## 异常日志文件名
log4j.appender.R.File = .log/yubao.log
log4j.appender.R.DatePattern='.'yyyy-MM-dd
log4j.appender.R.Append = true
## 只输出ERROR级别以上的日志!!!
log4j.appender.R.Threshold= ERROR
log4j.appender.R.layout = org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern =%-d{yyyy-MM-dd HH:mm:ss} [%c:%L]-[%p] %m%n
接下来配置log4j为日志实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
接下来测试一下,我们测试的信息就会有一个日志生成,在对应路径下也可以找到文件
简单使用:
用日志来代替sout,日志相比于sout有许多优点,我们之前配置了这个日志可以写在文件里,所以我们用日志输出的时候,日志文件里也可以找到,并且日志有优先级等等。
@Test
public void logTest(){
logger.info("this is a info");
logger.debug("this is an debug");
logger.error("this is an error");
}
五、分页
Sql分页
使用Limit分页
select * from mybatis.user limit #{startIndex},#{pageSize};
使用Mybatis分页
-
接口
List<User> selectUserByLimit(Map<String,Integer> map);
-
Mapper.xml
<select id="selectUserByLimit" parameterType="map" resultMap="userMap"> select * from mybatis.user limit #{startIndex},#{pageSize}; </select>
-
测试
@Test public void testimit(){ SqlSession sqlSession=MybatisUtils.getSqlSession(); UserMapper userMapper=sqlSession.getMapper(UserMapper.class); HashMap<String,Integer> map=new HashMap<String, Integer>(); map.put("startIndex",0); map.put("pageSize",2); List<User> userList=userMapper.selectUserByLimit(map); for(User user:userList){ System.out.println(user); } sqlSession.close(); }
Java分页
之前本质上还是利用sql来实现分页,不符合面相对象的思想,我们可以利于Rowbounds这个类来实现分页,但是比第一种方法慢,项目里面不推荐这种方法。
<select id="findAllUser" resultMap="userMap">
select * from mybatis.user;
</select>
前面的代码跟之前查询所有 用户都是一样的,我们只需要在测试的时候改动
@Test
public void testSelect(){
RowBounds rowBounds =new RowBounds(0,2);
SqlSession sqlSession =MybatisUtils.getSqlSession();
List<User> userList=sqlSession.selectList("com.yubao.dao.UserMapper.findAllUser",null,rowBounds);
for(User user:userList){
System.out.println(user);
}
sqlSession.close();
}
六、使用注解开发
-
注解在接口上实现
public interface UserMapper { @Select("select * from user") List<User> getUser(); }
-
在配置文件中绑定
<mappers> <mapper class="com.yubao.dao.UserMapper"/> </mappers>
使用注解开发能够简化流程,但是一些复杂的操作就完成不了,比如之间的数据库字段与类属性名不一样的问题。
CRUD
在使用注解查询的时候
- 有多个参数的时候,需要再基本类型和String的前面加上@Param()
- Sql语句里面的参数是看@Param()里面的
- 引用类型不需要@Param
- 只有一个参数的时候也不需要@Param
增
@Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})") int insertUser(User user);
查
@Select("select * from user where id =#{id} and name =#{name}") User getUserById(int id,String name);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4ITvl34-1638790994410)(/Users/sleeping/Library/Application Support/typora-user-images/image-20211122204000255.png)]
七、Lombok
-
@Getter/@Setter: 作用类上,生成所有成员变量的getter/setter方法;作用于成员变量上,生成该成员变量的getter/setter方法。可以设定访问权限及是否懒加载等。
-
@ToString:作用于类,覆盖默认的toString()方法,可以通过of属性限定显示某些字段,通过exclude属性排除某些字段。
-
@EqualsAndHashCode:作用于类,覆盖默认的equals和hashCode
-
@Data:作用于类上,是以下注解的集合:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor
-
@AllArgsConstructor:生成全参构造器
-
**@NoArgsConstructor:生成无参构造器;**q
八、多对一处理
新建两张表
CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=INNODB
DEFAULT CHARSET=utf8INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); CREATE TABLE `student` ( `id` INT(10) NOT
NULL, `name` VARCHAR(30) DEFAULT NULL, `tid` INT(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO
`student` (`id`, `name`, `tid`) VALUES (1, '小明', 1); INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小红',
1); INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小张', 1); INSERT INTO `student` (`id`, `name`, `tid`)
VALUES (4, '小李', 1); INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);
对于多对一的情况我们又两种方法。
按照查询嵌套处理
首先是按照查询嵌套处理,这个对应的就是SQL里面的子查询
<select id="getStudent" resultMap="StudentTeacher">select * from student</select>
<resultMap id="StudentTeacher" type="com.yubao.pojo.Student">
<!--property就是实体类的属性,column就是数据库里面的字段名-->
<result property="id" column="id"></result>
<result property="name" column="name"/>
<!--这里student实体类里面有一个Teacher类,我们利用association来处理-->
<association property="teacher" column="tid" javaType="com.yubao.pojo.Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="com.yubao.pojo.Teacher">select * from teacher where id=#{id};</select>
思路:
- 查询所有学生信息
- Student里面的Teacher类与查询出来的tid不对应,我们就利用查询
根据resultMap的设计思想:
ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了
我们可以简化上面的代码,将resultMap里面的前两个result删掉,最后也能成功运行。
按照结果嵌套处理
<select id="getStudent2" resultMap="StudentTeacher2">select s.id sid,s.name sname,t.name tname,tid from student s
,teacher t where s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="com.yubao.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="com.yubao.pojo.Teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
</association>
</resultMap>
这个其实比上面的那个容易理解,这个使用resultMap把我们的Sql语句查询出来的结果与实体类做一个映射。Sql语句就是我们常用的联表查询,将查询的字段起个别名,然后再resultMap里面使用这个别名映射到Student的属性上。
九、一对多处理
@Datapublic
class Teacher {
private int id;
private String name;
private List<Student> students;
}
按照查询结果嵌套处理
<select id="getTeacher" resultMap="TeacherStudent">select s.id sid ,s.name sname,t.id tid,t.name tname from teacher
t,student s where tid=s.tid;
</select>
<resultMap id="TeacherStudent" type="com.yubao.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="com.yubao.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
按照结果嵌套处理
<select id="getTeacher2" resultMap="TeacherStudent2">select * from teacher where id=#{tid}</select>
<resultMap id="TeacherStudent2" type="com.yubao.pojo.Teacher">
<collection
property="students"
javaType="ArrayList"
ofType="com.yubao.pojo.Student"
select="getStudentByTeacher"
column="id">
</collection>
</resultMap>
<select id="getStudentByTeacher" resultType="com.yubao.pojo.Student">
select * from student where tid=#{id};
</select>
由于我们一个Teacher有许多Student,所以我们查出来的是一个集合,我们使用Collection而不是Association,Collection里面有一个ofType就是这个集合的里面包含的类。
十、动态SQL
10.1、环境搭建
在Mybatis文件已经配置好的情况下
- MySql创建一张表格
CREATE TABLE `blog`(`id` VARCHAR(50) NOT NULL ,`title` VARCHAR(100) NOT NULL,`author` VARCHAR(30) NOT NULL
,`create_time` DATETIME NOT NULL ,`views` INT(30) NOT NULL)ENGINE=INNODB DEFAULT CHARSET=utf8;
- 编写实体类
@Datapublic
class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
- 编写实体类对应的Mapper接口和Mapper.xml文件
在实体类的属性名与数据库的字段名不相同的时候,如果实体类里面使用的是根据数据库里面进行驼峰命名来的,我们可以在配置文件里设置
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="True"/>
</settings>
10.2、IF
在我们之前学习JavaWeb的时候,用JDBC来在java代码里面判断一些参数是否有值,如果有值那么就进行拼接SQL语句,来实现查询等操作。在Mybatis里我们可以更方便的实现这一操作。
<select id="selectBlog" parameterType="map" resultType="com.yubao.pojo.Blog">select * from blog where 1=1
<if test="title!=null">and title=#{title}
</if>
<if test="author!=null">and author =#{name}</if>
</select>
我们在写Mapper.xml文件的时候使用if就可以实现上述操作,
@Test
public void TestFour() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title", "Mybatis");
for (Blog blog : blogMapper.selectBlog(map)) {
System.out.println(blog);
}
sqlSession.close();
}
我们在map里面放不同的参数就可以查询出不同的结果出来,不用在java代码里判断,在进行方法的重载等操作,使用if就可以实现sql代码的复用。
10.3、where set
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除
上面我们在学习if的时候我们的sql语句里面的 where 1=1这里就是为了防止,如果map里面有title传进来不会出现 where and 这种情况。我们使用where标签就可以解决这个问题
<select id="selectBlog" parameterType="map" resultType="com.yubao.pojo.Blog">select * from blog
<where>
<if test="title!=null">and title=#{title}</if>
<if test="author!=null">and author =#{name}</if>
</where>
</select>
我们在使用update语句时候也会遇到一些问题就是set里面的逗号
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
<update id="updateAuthorIfNecessary">update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
10.4、chose
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="findActiveBlogLike" resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">AND title like #{title}</when>
<when test="author != null and author.name != null">AND author_name like #{author.name}</when>
<otherwise>AND featured = 1</otherwise>
</choose>
</select>
传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)
什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句
10.5、SQL片段
有的时候,我们可能将一些功能的部分抽取出来,方便复用!
- 使用SQL标签抽取公共的部分
<sql id="if-title-author"> <if test="title!=null" > and title=#{title} </if> <if test="author!=null"> and author =#{author} </if></sql>
- 在需要使用的地方使用Include标签引用即可
<sql id="if-title-author">
<if test="title!=null">and title=#{title}</if>
<if test="author!=null">and author =#{author}</if>
</sql>
注意事项:
- 最后基于单表来定义Sql片段
- 不要存在where标签
10.6、foreach
<select id="selectBlog" parameterType="map" resultType="com.yubao.pojo.Blog">select * from blog
<where>id in <foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach>
</where>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
我们在写到sql语句里面 含有in的时候,如果in里面有几百个数据我们手写不知道要写多久,我们就可以通过一个集合将数据传进去,然后通过foreach拼接到sql语句里面
十一、缓存
-
什么是缓存?
- 存在内存汇中的临时数据
- 将用户经常查询的数据放在缓存中,用户查询数据就不用从磁盘上查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
-
为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
-
什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
11.1、Mybatis缓存
-
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。
-
Mybatis系统默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开机(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高拓展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。
-
针对select
11.2、一级缓存
一级缓存的作用域是SqlSession
首次执行它时从数据库获取的所有数据会被存储在一段高速缓存中,今后执行这条语句时就会从高速缓存中读取结果,而不是再次查询数据库。MyBatis提供了默认下基于Java HashMap的缓存实现。
这条语句的执行结果被缓存,以后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQL。但是一旦执行新增或更新或删除操作,缓存就会被清除。下面将分情况来验证一下
第1种情况:同个session进行两次相同查询
public class Test {
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Person p1 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
Person p2 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
session.commit();
session.close();
}
}
结论:MyBatis只进行1次数据库查询。
==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
< Total: 1
User{id=1, name=‘Kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
User{id=1, name=‘Kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
第2种情况:同个session进行两次不同的查询。
public class Test {
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Person p1 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
Person p2 = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 2);
session.commit();
session.close();
}
}
结论:MyBatis进行两次数据库查询。
==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
< Total: 1
User{id=1, name=‘kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
==> Preparing: select * from t_person where id = ?
> Parameters: 2(Integer)
< Total: 1
User{id=2, name=‘Jim’, age=50, birthday=Sat Dec 06 17:12:01 CST 2010}
第3种情况:不同session,进行相同查询。
public class Test {
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session1 = sqlSessionFactory.openSession();
Person p1 = session1.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
SqlSession session2 = sqlSessionFactory.openSession();
Person p2 = session2.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 2);
session1.commit();
session2.commit();
session1.close();
session2.close();
}
}
结论:MyBatis进行两次数据库查询。
==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
< Total: 1
User{id=1, name=‘kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
==> Preparing: select * from t_person where id = ?
> Parameters: 2(Integer)
< Total: 1
User{id=2, name=‘Jim’, age=50, birthday=Sat Dec 06 17:12:01 CST 2010}
第4种情况:同个session,查询之后更新数据,再次查询相同的语句。
public class Test {
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Person p = null;
p = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
p.setAge(30);
session.update("cn.mybatis.mydemo.mapper.PersonMapper.updatePerson", p);
p = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
session.commit();
session.close();
}
}
结论:更新操作之后缓存会被清除:
==> Preparing: select * from t_person where id = ?
> Parameters: 1(Integer)
< Total: 1
User{id=1, name=‘kit’, age=23, birthday=Sun Oct 12 23:20:13 CST 2010}
==> Preparing: update t_person set age = ? where ID = ?
> Parameters: 30(Integer), 1(Integer)
< Updates: 1
==> Preparing: sselect * from t_person where id = ?
> Parameters: 1(Integer)
< Total: 1
User{id=1, name=‘kit’, age=30, birthday=Sun Oct 12 23:20:13 CST 2010}
小结
很明显,以上各种情况验证了一级缓存的概念,在同个SqlSession中,查询语句相同的sql会被缓存,但是一旦执行新增或更新或删除操作,缓存就会被清除。
引用:http://www.mybatis.cn/archives/744.html
11.3、二级缓存
既然有了一级缓存,那么为什么要提供二级缓存呢?我们知道,在一级缓存中,不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。
另外,Spring和MyBatis整合时,每次查询之后都要进行关闭sqlsession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
步骤:
- 开启全局缓存
<!--显式的开启全局缓存--><setting name="cacheEnabled" value="true"/>