HelloWorld
- pom
(依赖+资源过滤问题)
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
- 表设计
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user` (`id`, `name`, `age`) values('1','林更新','18');
- 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>
<typeAliases>
<package name="com.kuang.pojo"/> <!-- 默认首字母小写的类名 -->
<typeAlias type="com.kuang.pojo.User" alias="u"/>
</typeAliases>
<!-- environments 配置多个environment,供切换 -->
<environments default="development"> <!--default指定一个默认环境的id-->
<environment id="development">
<!-- type:处理事务方式, 大多数情况使用jdbc,另一种 managed:将事物交给其他组件托管,如spring-->
<transactionManager type="JDBC" />
<!-- 配置数据库连接类型 常见的有:POOLED、UNPOOLED、JDNI -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<!-- useSSL true当文本不能全部显示时,后面用“…”来表示 false文本将自动换行
这里的&报错,是因为IDEA将“&”当成了特殊符号。改为“&”
url后跟问号,跟参数进一步设置
?useSSL=true&useUnicode=true&characterEncoding=utf-8 -->
<property name="url" value="jdbc:mysql://localhost:3306/cpt" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 使用相对于类路径的资源引用(推荐) -->
<mapper resource="com/baidu/xxx/UserMapper.xml"/>
<!-- 注册一个绝对路径的的Mapper.xml文件(不推荐) -->
<mapper url="file:///E:/UserMapper.xml"/>
<!-- 使用映射器接口实现类的完全限定类名,需要xml配置文件名称和接口名称一致,且同路径下 -->
<mapper class="com.baidu.xxx.UserMapper"/>
<!-- 将包内的映射器接口实现全部注册,需要配置文件名称和接口名称一致,且同路径下 -->
<package name="com.baidu.xxx"/>
</mappers>
</configuration>
- Entity包中User.java
public class User {
private int id;
private String name;
private int age;
//toString、全参构造、set、get、toString 此处全部省略。。。实际开发必须有,不然代码报错
}
- MybatisUtil.java
public class MyBatisUtil {
public static SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);//使用类加载器加载mybatis的配置文件
//构建sqlSession的工厂 build方法还可以追一个参数,指定环境environment标签的id
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
return sqlSessionFactory;
}
public static SqlSession getSqlSession() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
return sqlSessionFactory.openSession();//若是设置为opsenSession(true)默认自动提交事务,就不需要手动commit
}
}
- 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 namespace="com.baidu.cpt.mapper.UserMapper">
<!-- namespace为mapper指定唯一的id,通常设置成包名+sql映射的文件名-->
<!-- 这里namespace不建议写xml文件全名,这种方式已过时,建议写接口全名,使用面向接口的优秀方式 -->
<!-- parameterType指明查询时使用的参数类型。如果参数只有1个则用简单类型,参数为多个时可用Map集合或实体对象,
占位符必须用对象的属性名;
resultType表示查询结果将要封装成的实体类,也可以为对象类型、Map类型、基本类型 -->
<select id="selectOneUser" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
<!-- useGeneratedKeys 设置true,自动封装表的下一个自增值到对象中对应的属性
keyProperty 指定对象的哪个属性 对应的是表的主键
特别注意的是表设计没有将id设置为自增,这里取不到自增值,会给一个id的默认值0,连续插入2次id为0就会主见重复报错
如果传的参数User对象只构造了name和age,sql缺少id会报错
keyColumn="id" 指对应的表字段 -->
<insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into user(id, name, age) values(#{id}, #{name}, #{age})
</insert>
<insert id="insertUser2" parameterType="user" >
<selectKey keyProperty="id" order="BEFORE" resultType="int"> <!-- before在sql执行之前拿到,然后供sql使用 -->
select CEILING(RAND()*10000)
</selectKey>
insert into user(id, name, age) values(#{id}, #{name}, #{age})
</insert>
<delete id="deleteUser">
delete from user where id=#{id}
</delete>
<update id="updateUser" parameterType="user" >
update user set nam e= #{name},age = #{age} where id = #{id}
</update>
<select id="selectAllUser" resultType="user">
select * from user
</select>
</mapper>
- Test.java
public class Test {
public static void main(String[] args) throws Exception{
SqlSession session = MyBatisUtil.getSqlSession();//至此得到一个session
//Unhandled Exception:java.io.IOException 是因为少了throws Exception或者try/catch
//org.apache.ibatis.binding.BindingException: Type interface xxx is not known to the MapperRegistry
//上面的错误时因为namespace和接口名不相同,必须改为相同
String namespace = "com.baidu.cpt.mapper.UserMapper.";//注意后面有一个点,为了和后面的mapper文件中的id连接
String statement4 = namespace+"selectOneUser";
User user = session.selectOne(statement4,1);
//selectOne sql中需要一个参数就传一个,这里查出来几个字段,都会按照实体类构造方法按顺序进行赋值
System.out.println("selectOne:"+user);
String statement1 = namespace+"insertUser";//映射sql的标识字符串
int insert = session.insert(statement1,new User(10,"张三",18));
//insert 这里为什么插入后id并不是指定的10,因为该namespace中sql语句中没有插入id字段,而建表时主键id是自动增长的。
session.commit(); //默认增删改 必须手动提交提交事务
System.out.println("insert:"+insert);
String statement2 = namespace+"deleteUser";
int delete = session.delete(statement2,2);
session.commit(); //默认增删改 必须手动提交提交事务
System.out.println("delete:"+delete);
String statement3 = namespace+"updateUser";
int update = session.update(statement3,new User(1,"林更新",18));
session.commit(); //默认增删改 必须手动提交提交事务
System.out.println("update:"+update);
String statement5 = namespace+"selectAllUser";
List<User> list = session.selectList(statement5);
System.out.println("selectList:"+list);
session.close();//必须释放session
}
}
三种helloworld方式区别
1,xml
<!-- mybatis-config.xml 中注册:UserMapper.xml,使用命名空间来找到 select 标签 -->
<mappers>
<package name="com.baidu.cpt.mapper"/>
</mappers>
2,xml + interface(推荐,且最流行)
// 1,xml不变
// 2,增加接口
public interface UserMapper {
public List<User> selectAllUser(); // 方法名必须和mapper文件的select标签id一致,mapper代理要求名字保持一致
}
// 3,测试方法和纯xml方式也不同,使用反射,根据接口类, 而不是namespace
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> lists = userMapper.selectAllUser();
for (User u : lists ) {
System.out.println(u);
}
// 4,不再是注册xml,而是注册接口
<mappers>
<package name="com.baidu.cpt.mapper"/>
</mappers>
3,interface + anotation
只需要interface,不再需要userMapper.xml,注解将sql写在方法上,当有注解时,默认使用方式三,但只适用简单sql
注意要同名,因为如下:
本质:反射机制实现
底层:动态代理!
// 1,新建一个接口文件,调用的方法不再是select标签,而是注解中sql
public interface UserMapperNew {
@Select ( "select * from user" )
public List<User> selectAllUserNew() throws Exception;
@Insert( "insert into user(name, age) values(#{name}, #{age})" )
public int insertUser(User user) throws Exception;
@Delete( "delete from user where id=#{id}" )
public int deleteUser(int id) throws Exception;
@Update( "update user set name=#{name},age=#{age} where id=#{id}" )
public int updateUser (User user,int id) throws Exception;
@Select( "select * from user where id = #{id}" )
public User selectOneUser(int id) throws Exception;
}
// 2,测试方法,调接口的方法,方法名无需和select标签一致,因为使用的该方法的注解中的sql,与userMapper.xml文件无关。
UserMapperNew userMapper = session.getMapper(UserMapperNew.class);
List<User> lists = userMapper.selectAllUserNew();
for (User u : lists ) {
System.out.println(u);
}
mybatis是怎么找到mapper中的sql
// 使用类加载器加载mybatis的配置文件
InputStream is = Test1.class.getClassLoader().getResourceAsStream(“conf.xml”);
// 构建sqlSession的工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
// 通过工厂类来得到一个sqlSession,
SqlSession sqlSession = sqlSessionFactory.openSession();
String statement = “com.sl.mapper.ProductMapper.insertProduct”;
int count = session.insert(statement, product);
纯xml方式是根据namespace+id找到对应的sql来执行
ProductMapper productMapper = session.getMapper(ProductMapper.class);
List listProduct = productMapper.selectAllProduct();
xml+接口方式,是通过动态代理找到mapper中的id来执行sql
配置文件解析
标签有严格的顺序
configration
properties
settings
typeAliases
typeHandlers类型处理器
objectFactory对象工厂, objectWrapperFactory, reflectorFactory
plugins
environments
environment环境子属性对象
transactionManager事务管理
dataSource数据源
databaseIdProvider
mappers
configration
引入外部配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=123456
<configuration>
<properties resource="db.properties"/>
<!-- properties 中可以使用子属性property自定义属性值,但优先会使用外部文件 -->
<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>
</configuration>
setting
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
一、二级缓存
执行了一次查询后,向数据库发送了一条sql,再次查询时发现还是只发送了一条sql,
那是因为上一条sql查到的数据放入了缓存中,第二次直接取出来。加快了效率减小了系统负担。
一级缓存(本地缓存),sqlSession级别的缓存,一级缓存是默认一直开启的.
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据(参数与上一次相同,不然得到结果不同,自然缓存中不存在,无法得到),直接从缓存中取,没必要再去查询数据库
一级缓存的几种失效情况
1. sqlSession更换,不是同一个session都不存在缓存的现象
2. sqlSession相同,查询条件发生改变
3. sqlSession相同,但两次查询之间执行了增删改操作,哪怕操作的条件不同,因为增删改有可能对原来数据有影响
4. sqlSession相同,手动清除了一级缓存session.clearCache(),会进入二级缓存
二级缓存(全局缓存):
1,参与代码执行过程的实体类需要实现序列化接口,包括entity关联的entity类 implements Serializable)
基于namespace,namespace级别的缓存。
2,会话查到的数据默认存在一级缓存中,但如果关闭的话就会存入二级缓存中,不关闭就只存在一级缓存中,默认就是开启一级缓存。
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
还需要在 Mapper 的xml 配置文件中加入 <cache/>标签,当然可以自定义一些参数
<cache readOnly="true" size="500" flushInterval="120000" eviction="LRU" />
size : 缓存存放多少数据。最大512
flushInterval : 缓存刷新间隔时间(毫秒时间),默认不清空
readOnly : 缓存是否只读;
eviction : 缓存回收策略
LRU: 最近最少使用,移除最长时间不被使用的对象,默认策略
FIFO: 先进先出,按对象进入缓存的顺序来移除他们
SOFT: 软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK: 弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
true : MyBatis 认为所有从缓存中获取数据的操作都是只读。不会修改数据。MyBatis 为了加快获取速度,直接就会将数据在缓存中的引用交给用户。
false : 非只读。MyBatis 会识别到数据可能会被修改,用户获得的数据是MyBatis 利用序列化和反序列化克隆的新数据交给用户(建议实体类实现序列化接口)。
SqlSession session1 = sf.openSession();
SqlSession session2 = sf.openSession();
UserinfoMappe mapper1 = session1.getMapper(UserinfoMapper.class);
UserinfoMappe mapper2 = session2.getMapper(UserinfoMapper.class);
Userinfo userinfo1 = mapper1.selectOne(101);
//会话关闭时,缓存会保存到二级缓存中,不关闭就进不去二级缓存中,就实现不了取数据
session1.close();
//从二级缓存中获取数据,不会从新发送新的 SQL 语句
Userinfo userinfo2 = mapper2.selectOne(101);
session2.close();
深入讲解:https://www.bilibili.com/video/BV1oJ411s7eS?from=search&seid=12127812127908391645
鉴别器
将查出来的数据,按照条件进行数据分类,第一类封装为A,第二类封装为B
比如:查出来sex是女生就返回封装类型A,查出来sex是男生返回封装类型B
<mapper namespace="com.xxx.entity.mapper.EmpMapper">
<resultMap id="empMap" type="com.xxx.entity.Emp">
<id property="id" column="id" />
<result property="empName" column="emp_name" />
<result property="sex" column="sex" />
<association property="empCard" column="id"
select="com.xxx.entity.mapper.EmpCardMapper.getEmpCardByEmpId" />
<collection property="projectList" column="id"
select="com.xxx.entity.mapper.ProjectMapper.findProjectByEmpId" />
<discriminator javaType="int" column="sex">
<!-- 这里设置为简单类型的话就不写resultMap,写resultType就可以 -->
<case value="1" resultMap="maleEmpMap" />
<case value="2" resultMap="femaleEmpMap" />
</discriminator>
</resultMap>
<select id="getEmp" parameterType="int" resultMap="empMap">
select id, emp_name as empName, sex from emp where id > #{id}
</select>
<resultMap id="maleEmpMap" type="com.xxx.pojo.MaleEmp" extends="empMap">
<collection property="prostateList" select="com.xxx.mapper.MaleEmpMapper.findUserList" column="id" />
</resultMap>
<resultMap id="femaleEmpMap" type="com.xxx.pojo.FemaleEmp" extends="empMap">
<collection property="uterusList" select="com.xxx.mapper.FemaleEmpMapper.findUserList" column="id" />
</resultMap>
</mapper>
CDATA[转义字符]
将转义字符放在 <![CDATA[转义字符]]> 中
写的sql中有一些特殊的字符的话,在解析xml文件的时候会被转义,但我们不希望他被转义,使用XML语法<![CDATA[ ]]>
<select id="allUserInfo" parameterType="java.util.HashMap" resultMap="userInfo1">
<![CDATA[
SELECT * WHERE 1=1 AND newsday > #{startTime} AND newsday <= #{endTime}
]]>
</select>
但是有个问题那就是 <if test=""></if> <where></where> <choose></choose> <trim></trim> 等这些标签都不会被解析,
所以我们只把有特殊字符的语句放在 <![CDATA[ ]]> 尽量缩小 <![CDATA[ ]]> 的范围
SELECT state FROM emp WHERE state <![CDATA[ <> ]]>'effect'
调用存储过程
mysql
call pro_create_aaa( #{id}, #{age} )
{call pro_create_aaa( #{id}, #{age} )}
sqlserver
{call pro_create_aaa #{project_id}, #{cut_off_time}}
输入参数out,输入参数in
什么时候使用,jdbcType=NUMERIC
statementType="CALLABLE"
<insert id="addUser" parameterType="user" statementType="CALLABLE">
{call insert_user(
#{id, mode=OUT, jdbcType=INTEGER},
#{name, mode=IN,jdbcType=NUMERIC},
#{age, mode=IN,jdbcType=NUMERIC})}
</insert>
.trim() .toString()
select * from user where 1=1
<where> 1=1
<if test="year_id != null and year_id !='' ">
and renzhu.year_id = #{year_id}
</if>
<if test="pcmc_name != null and deptName.trim() == '信息服务部'.toString() ">
and p2.username like '%${name}%'
</if>
</where>
#$区别
sql注入
原来:SELECT * FROM user WHERE username LIKE '%${value}%'
传的参数:List<User> list=userMapper.findUserByName("' or '1'='1");
实际sql:SELECT * FROM student WHERE stu_name LIKE '%' or '1'='1%'
'1'='1%'相当于查询全部:SELECT * FROM student
在JDBC中,主要使用的是两种语句,
一种是支持参数化和预编译的PrepareStatement,能够支持原生的Sql,也支持设置占位符的方式,参数化输入的参数,防止Sql注入,
一种是支持原生Sql的Statement,有Sql注入的风险。
• #{value} 这种取值是编译好SQL语句再取值,是安全的;
在预处理时,会把参数部分用一个占位符 ? 替代,其中 value 表示接受输入参数的名称。
• ${
value} 这种是取值以后再去编译SQL语句,是非安全的,
表示使用拼接字符串,将接受到参数的内容不加任何修饰符拼接在 SQL 中,将引起 SQL 注入问题。
1, #自动加单引号,$原样输出
SELECT * FROM SYS_PUB_ORGANIZATION WHERE ORG_NAME LIKE #{orgname}
--使用#解析为:SELECT * FROM SYS_PUB_ORGANIZATION WHERE ORG_NAME = '长亮科技'
SELECT * FROM SYS_PUB_ORGANIZATION WHERE ORG_NAME LIKE '%${orgname}%'
--使用$解析为:SELECT * FROM SYS_PUB_ORGANIZATION WHERE ORG_NAME LIKE 长亮科技
一般能用 # 就不要使用$,若一定使用${xxx},需要手工做好过滤工作,来防止sql注入
$方式一般用于传入不需要加双引号的sql片段,比如数据库对象和表名
one to one
association javaType 【JavaType用来指定实体类中属性的类型】
学生属性有一个tid,表示他的老师,老师又是一个对象,这种称之为一对一
CREATE TABLE `teacher`(
`id` int(10) Not null,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT 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=utf8
INSERT 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);
方式1:按照查询嵌套处理
此方式查询次数较多,效率低
<!-- 思路:
1、查询所有的学生信息
2、根据查询出来的学生的id的tid,寻找对应的老师! -子查询 -->
<select id="getStudent" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="com.rui.pojo.Student">
<!--复杂的属性,我们需要单独处理 对象:association 集合:collection-->
<!--此处是学生关联了一个老师对象 association javaType select -->
<association property="teacher" column="tid" javaType="com.rui.pojo.Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="com.rui.pojo.Teacher">
select * from teacher where id = #{id}
</select>
方式2:按照结果嵌套处理:连表查询
<!--按照结果嵌套处理
与上一种方式差别是:sql改为关联查询,少一个select标签-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="com.rui.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="com.rui.pojo.Teacher">
<result property="id" column="tid"></result>
<result property="name" column="tname"></result>
</association>
</resultMap>
one to many
collection ofType 【ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型】
比如:一个老师拥有多个学生!
对于老师而言,就是一对多的关系!
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
@Data
public class Student {
private int id;
private String name;
private int tid;
}
方式1:按照结果嵌套处理
<!--按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="com.rui.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--复杂的属性,我们需要单独处理 对象:association 集合:collection
javaType="" 指定属性的类型
集合中的泛型信息,我们使用ofType获取
-->
<collection property="students" ofType="com.rui.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
方式2:按照查询嵌套处理
collection javaType ofType
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="com.rui.pojo.Teacher">
<collection property="students" javaType="ArrayList" ofType="com.rui.pojo.User" select="getStuById" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="com.rui.pojo.Student">
select * from mybatis.student where tid = #{tid}
</select>
分页
方式1:使用Limit分页,给sql传入startIndex, pageSize
List<User> getUserByLimit(Map<String,Integer> map);
<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
方式2:RowBounds分页
由于 java 允许的最大整数为 2147483647,所以 limit 能使用的最大整数也是 2147483647,一次性取出大量数据可能引起内存溢出,所以在大数据查询场合慎重使用
RowBounds并不是一次性取出全部数据,因为 底层是对 JDBC 的封装,在 JDBC 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,为了避免溢出问题
List<User> getUserByRowBounds();
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(1, 2);
//通过java代码层面实现分页
List<User> userList = sqlSession.selectList("com.rui.dao.UserMapper.getUser",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
方式3:pagehelper
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
<!-- pagehelper的依赖包:jsqlparser -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.5</version>
</dependency>
在mybatis核心配置xml中
<configuration>
<plugins>
<!-- 配置mybatis分页插件 -->
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 4.0.0以后版本可以不设置该参数 -->
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/>
<!-- 支持通过Mapper接口参数来传递分页参数 -->
<property name="supportMethodsArguments" value="false"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="none"/>
</plugin>
</plugins>
</configuration>
@Test
void testselectall() {
//1.使用PageHelper类设置起始页和每页显示的条数
int pageNum=5;//当前页码 从网页中可以获取
int pageSize=3;//pageSize:自定义
PageHelper.startPage(pageNum,pageSize);
//2调用查询所有的方法
List<User> list=userMapper.selectall();
for (User user : list) {
System.out.println(user);
}
//3.把查询的结果封装到Pageinfo中
PageInfo<User> pageinfo=new PageInfo<>(list,4);
System.out.println("上一页:"+pageinfo.getPrePage());
System.out.println("当前页:"+pageinfo.getPageNum());
System.out.println("下一页:"+pageinfo.getNextPage());
System.out.println("总页数:"+pageinfo.getPages());
int[] navigatepageNums = pageinfo.getNavigatepageNums();
for (int i : navigatepageNums) {
System.out.print(i+"\t");
}
}
日志
方式一:mybatis内置实现
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
方式二:Log4j(推荐)
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.properties
# 主要配置
log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.apache=INFO
### 解释 ###
## log4j.rootLogger ##
# 作用:控制日志输出的级别、输出的位置。
# 参数:级别、输出的位置
# 级别: DEBUG < INFO < WARN < ERROR (除了这些,还有其他级别)
# 级别作用:以选DEBUG级别为例,大于DEBUG级别的信息(INFO/WARN/ERROR)也会输出
# 选用DEBUG级别的原因:是跟Mybatis的源码有关(你会发现最低的级别是DEBUG)。
# 查看源码1:可以将Mybatis的源码文件夹直接导入到项目中的Mybatis的jar包引用中。具体操作:右击项目--》
properties--》Java Build Path --》选择顶部的Libraries页面 --》点击展开mybatis.jar
--》选择Source attachment --》点击右边的Edit --》External location --》 External File...
-》选择Mybatis源码文件夹 --》OK --》Apply
# 查看源码2:项目中的Referenced Libraries --》 mybatis.jar
--》org.apache.ibatis.logging.jdbc 里面的class文件
## log4j.appender.Console ##
# 作用:真正控制,日志输出到什么地方,取决于参数用了什么类
# log4j.appender.名称 名称可以自定义,而 log4j.rootLogger中的位置就要写这个名称
## log4j.appender.Console.layout ##
# 作用:以什么布局方式输出日志,这里是自定义的布局
## log4j.appender.Console.layout.ConversionPattern ##
# 作用:就是你自定义的布局
# %d 是产生日志的时间。 %t 是产生这个日志所处的线程的名称。
# %p 是输出日志的级别,%-5p 5,就是输出5位字符,不足补空格 -,补的空格在右边
# %c 是输出的这个日志所在的类的全名。 %m 是你附加的信息。 %n 是换行
## log4j.logger.org.apache 对 org.apache ##
# 作用:的输出级别进行设置(也可以换成其他包),**此处**的会覆盖掉log4j.rootLogger设置的级别。就是选择我们需要看的信息。
生命周期和作用域
生命周期,和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
一旦创建了SqlSessionFactory,就不再需要它了
局部变量
mybatis运行流程SqlSessionFactory:
可以想象为:数据库连接池
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
因此SqlSessionFactory的最佳作用域是应用作用域。
最简单的就是使用 单例模式 或者静态单例模式
SqlSession
连接到连接池的一个请求!
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
用完之后需要赶紧关闭,否则会占用资源
SqlSessionFactory
这里的每一个Mapper,就代表一个具体的业务!
传参数几种方式
此处针对的都是传递多个参数的情况,当传递单个参数时,传的参数名和mapper.xml中接收参数名都只有一个,
底层会直接视为同一个,进行处理。
====================传参方式1:将多个参数封装到一个map对象中,然后通过key取value==================
public interface UserDao {
public List<User> selectUser(HashMap map) throws Exception;
}
public void selectUser() throws Exception {
SqlSession session = MybatisUtils.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
HashMap<String,String> map = new HashMap<String, String>();
map.put("abc","1");
List<User> list = mapper.selectUser(map);
...
}
<select id="selectUser" parameterType="map" resultType="user">
select * from user where id = #{
abc}
</select>
=====================传参方式2:从接口获取的参数多个,通过下标索引index取值==================
public interface UserDao {
public List<User> selectUser(String id, String name) throws Exception;
}
<select id="selectUser" parameterType="user" resultType="user">
select * from user where id = #{
0}
</select>
=====================传参方式3:注解来告诉xml取对应名字的参数======
public interface UserDao {
public List<User> selectUser(@Param("id") String id, @Param("name") String name) throws Exception;
}
<select id="selectUser" parameterType="map" resultType="user">
select * from user where id = #{
id} and name = #{
name}
</select>
=================传参方式4:实体类,比如参数封装为User,通过user的对应属性获取值==================
public interface UserDao {
public List<User> selectUser(User u) throws Exception;
}
<select id="selectUser" parameterType="user" resultType="user">
select * from user where id = #{
id} and name = #{
name}
</select>
=================传参方式5:匿名参数 按顺序传递值 arg0等同于param1 arg1等同于param2================
List<User> selectUser(int age, String name)
<select id="selectUser" resultType="user" parameterType="user" databaseId="mysql">
select * from user where 1=1
<if test="arg0 != null">
and age = #{
param1}
</if>
<if test="arg1 != null">
and name = #{
param2 }
</if>
</select>
=====================传参方式6:使用JSON传递参数========================
List<User> selectUser(JSONObject params)
<select id="selectUser" resultType="user" parameterType="com.alibaba.fastjson.JSONObject">
select * from user where 1=1
<if test="id != null">
and id = #{
id}
</if>
<if test="name != null">
and name = #{
name}
</if>
</select>
========================复杂情况:参数类型为对象+集合========================
@Data
public class Department {
private Long id;
private String deptName;
private String descr;
private Date createTime;
List<Employee> employees;
}
List <Employee> findByDepartment(@Param("department")Department department);
<select id="findByDepartment" resultMap="BaseResultMap" parameterType="com.wg.demo.po.Department">
select * from employee where dept_id =#{
department.id} and age in
<foreach collection="department.employees" open="(" separator="," close=")" item="employee">
#{
employee.age}
</foreach>
</select>
PS:方式4传递的参数是vo实体类,和 方式1 封装为一个map对象,甚至网上说的封装一个专门用来存参数的vo对象Condition.java,
其实具体做法都类似,是一个做法。
模糊查询
第1种:在Java代码中添加sql通配符%
string str = "%张%";
list<User> list = mapper.selectlike(str);
<select id="selectlike">
select * from foo where bar like #{value}
</select>
第2种:在sql语句中拼接通配符%,但会引起sql注入
string str = "张";
list<User> list = mapper.selectlike(str);
<select id="selectlike">
select * from user where name like '%${value}%'
</select>
全部标签
databaseIdProvider
mybatis-config.xml 的 <databaseIdProvider>标签会设置几个数据库的别名
注意:此时需要引入 oracle 和 sqlserver 驱动jar
mapper.xml 中 databaseId="mysql" 动态指定一个value
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle" />
<property name="SQL Server" value="sqlserver" />
</databaseIdProvider>
<select id="selectUser" resultType="user" parameterType="user" databaseId="mysql">
select * from user where 1=1
<if test="_parameter != null">
<!-- 单个参数时_parameter就是这个参数 对象参数时,会封装为map,通过_parameter.id传id -->
and id = #{_parameter.id }
</if>
<if test="_databaseId == null">
<!-- _databaseId 就是 databaseIdProvider 中 value 的值 -->
and name = #{_databaseId }
</if>
</select>
bind,if
<!-- bind标签可以使用OGNL表达式创建一个变量并将其绑定到上下文中。
需求:
concat函数连接字符串,在MySQL中,这个函数支持多个参数,但在Oracle中只支持两个参数。
由于不同数据库之间的语法差异,如果更换数据库,有些SQL语句可能就需要重写。针对这种情况
可以使用 bind 标签来避免由于更换数据库带来的一些麻烦。
<bind>属性:
name:为绑定到上下文的变量名
value:为OGNL表达式。
使用bind标签拼接字符串不仅可以避免因更换数据库而修改SQL,也能预防SQL注入。 -->
<!--原代码-->
<if test="name !=null and name!=''">
and name like concat('%',#{name},'%')
</if>
<!--改进后代码-->
<if test="name !=null and userName!='' ">
<bind name="userNameFinal" value="'%'+name+'%'"/>
and name like #{nameFinal}
</if>
concat标签,在mysql中,可以拼接多个字符,但是如果有一天,你的数据库突然变成了oracle,
concat就会报错了,因为它只能接受两个参数。
这个时候,bind同样适用,如下:
<if test=" userName != null and userName ! = ">
and username like concat ( '1',#{userName},'2' )
</if>
可以改成:
<if test=" userName != null and userName !=">
<bind name= " userNameLike ” value = ”'1'+ userName + '2'”/>
and username like #{userNameLike}
</if>
<select id="queryBlogIF" parameterType="map" resultType="com.rui.pojo.Blog">
select * from mybatis.bolg where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
choose,when,otherwise
choose中的and条件,会自动处理是否需要拼接“and”问题
choose中只会选择一个满足的when生效
当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql
类似于Java 的 switch 语句,otherwise 则为 default
<select id="query" parameterType="map" resultType="user">
select * from user
<where>
<choose>
<when test="id != null">
id =#{id}
</when>
<when test="name!=null">
and name = #{name}
</when>
<otherwise> and age = #{age} </otherwise>
</choose>
</where>
</select>
where,set,trim
where之后所有条件都不符合,sql是select * from user where,<where>此时会自动去掉where保证语法正确
select * from mybatis.bolg
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
set会删除最后一个逗号,保证语法正确
<update id="updateBlog" parameterType="map">
update user
<set>
<if test="title != null"> title = #{title}, </if>
<if test="author != null"> author = #{author}, </if>
</set>
where id = #{id}
</update>
<!-- 有符合的条件,就自动加前缀where
prefixOverrides="and"会删除第一个and, 保证语法正确
suffixOverrides=","会删除第一个, 保证语法正确 -->
<trim prefix="where" prefixOverrides="and">
<if test="state != null">
and state = #{state}
</if>
<if test="title != null">
and title like #{title}
</if>
<if test="author != null and author.name != null">
and author_name like #{author.name}
</if>
</trim>
sql include
<sql id="common">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="user">
select * from user where 1=1
<where>
<include refid="common"></include>
</where>
</select>
foreach
查询 id=1 or id=3 or id=5
select * from user where 1=1 and
<foreach item="id" index="index" collection="ids"
open="(" separator="or" close=")">
#{id}
</foreach>
<!-- 表示从ids这个参数中取出id, 拼接成 (id=1 or id=2 or id=3) -->
<!-- select * from mybatis.bolg where 1=1 and (id=1 or id=2 or id=3)
我们现在传递一个万能的map,这个map中可以存在一个map -->
<select id="queryBlogForeach" parameterType="map" resultType="com.rui.pojo.Blog">
select * from mybatis.bolg
<where>
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
foreach元素的属性主要有 item,index,collection,open,separator,close
item表示集合中每一个元素进行迭代时的别名
index指定一个名字,用于表示在迭代过程中,每次迭代到的位置
open表示该语句以什么开始
separator表示在每次进行迭代之间以什么符号作为分隔符
close表示以什么结束
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,
但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
1.如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2.如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3.如果传入的参数是多个的时候,我们就需要把它们封装成一个Map或者Object
再举一个例子
<update id="updateLxdh" parameterType="java.util.HashMap">
这种方式是执行了多个update语句,上一种方式是一个语句,速度肯定是单个语句快
<foreach collection="list/array/map" item="item" index="index" separator=";">
update hr_user_base_info set lxdh =#{item.lxdh} where usercode =#{item.usercode}
</foreach>
</update>
resultMap
当查到的结果集和parameterType中实体类字段不一致时,表示此时并不是一个单独的实体,而是多个属性的汇总了,就需要resultMap来表明具体某个属性对应sql的结果集中哪个字段
方式1:sql起别名
数据库是order_id,实体类属性是id
<select id="selectOrder" parameterType="int"
resultType="me.gacl.domain.Order">
select order_id id, order_no orderNo from orders where order_id=#{id}
</select>
方式2:resultMap
结果集映射
<!-- 根据id查询得到一个order对象,使用这个查询是可以正常查询到我们想要的结果的,
这是因为我们通过<resultMap>映射实体类属性名和表的字段名一一对应关系 -->
<select id="selectOrderResultMap" parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<!-- 通过<resultMap>映射实体类属性名和表的字段名对应关系 -->
<resultMap type="me.gacl.domain.Order" id="orderResultMap">
<!-- 用id属性来映射主键字段 property是实体类属性,column是表字段-->
<id property="id" column="order_id"/>
<!-- 用result属性来映射非主键字段 -->
<result property="orderNo" column="order_no"/>
<!-- price属性名字段名是相同的,就没必要写了,可以注释掉 -->
<result property="price" column="price"/>
</resultMap>
对比Hibernate
Hibernate劣势
- 代码虽然强大但过于简略,并不清楚到底做了什么,不利于排查问题
作为程序员,我们希望每一行代码都知道它到底在干什么。尤其是数据库访问的代码,往往系统的瓶颈就在这些地方产生,每一行都不能小看。hibernate早期版本的部分代码,比我想象的复杂和强大很多。的确很多地方Hibernate可以强大的只用一行代码解决很多问题,这里既有hibernate本身的逻辑,也有你应用的逻辑,如果这一行产生了问题,很难排查原因。 - 需求迭代后项目修改巨大
开发时节约了很多时间,但是它对你的业务逻辑和数据模型依赖的太高了。短期没啥问题,但随着项目的变迁,在维持时,会发现你代码特别脆弱,随便改一处数据库,整个java项目可能要大改。
相比之下Mybatis对程序员更友好