赞
踩
mybatis是一款优秀的持久层框架,它支持定制化的SQL,存储过程以及高级映射(多表)。mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。它对JDBC的操作数据库的过程进行封装,使开发者只关注SQL本身,而不需要花费精力去处理例如注册驱动,创建connection,创建statement,手动设置参数,结果集检索等JDBC繁杂的过程代码。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
1.配置文件:
全局配置文件(核心配置文件):mybatis-confing.xml,作用:配置数据源(配置数据库连接信息),引入映射文件:XXXMapper.xml,作用:配置sql语句,参数,结果集封装类型等。
2.sqlsessionFactory:
作用:获取sqlsession
通过new SqlsessionFactoryBulider().build(inputStream)来构建,inputStream:读取配置文件的io流。
3.sqlsession:
作用:执行CRUD操作
4.Executor:
执行器,sqlsession通过调用它来完成具体的CRUD操作
5.mapper statement
在映射文件里面配置,包含3部分内容:
具体sql,sql执行所需要的参数类型,sql执行结果的封装类型
参数类型和结果集封装类型包括3种:
hashmap,基本数据类型和pojo
Object Relational Mapping 对象关系映射
1.通过xml映射
2.通过注解
利用mybatis框架,从MySQL中查询所有的用户
create table user (
id int primary key auto_increment,
username varchar(20) not null,
birthday date,
sex char(1) default '男',
address varchar(50)
);
insert into user values (null, '孙悟空','1980-10-24','男','花果山水帘洞');
insert into user values (null, '白骨精','1992-11-12','女','白虎岭白骨洞');
insert into user values (null, '猪八戒','1983-05-20','男','福临山云栈洞');
insert into user values (null, '蜘蛛精','1995-03-22','女','盤丝洞');
select * from user;
<!--mybatis核心包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.0</version> </dependency> <!--logback日志包--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.18</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency>
<?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> <!--mybatis环境的配置--> <environments default="development"> <!--通常我们只需要配置一个就可以了, id是环境的名字 --> <environment id="development"> <!--事务管理器:由JDBC来管理--> <transactionManager type="JDBC"/> <!--数据源的配置:mybatis自带的连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/db4"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <!--加载映射文件,放到src下即可--> <mapper resource="userMapper.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.czy.dao.UserMapper">
<!--
查询语句
resultType:返回的实体类的类型,类全名
-->
<select id="findAll" resultType="com.czy.pojo.User">
select * from user
</select>
</mapper>
package com.czy.dao;
import com.czy.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> findAll();
}
将接口和映射文件绑定:
映射文件的namespace值 == 接口的全路径
映射文件的sql语句的id值 == 接口中的方法名
package com.czy.pojo; import java.util.Date; public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; public User(Integer id, String username, Date birthday, String sex, String address) { this.id = id; this.username = username; this.birthday = birthday; this.sex = sex; this.address = address; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
在mybatis中一个会话相当于一次访问数据库的过程,一个会话对象类似于一个connection连接对象。
1.SqlsessionFactoryBuilder:这是一个临时对象,用完就不需要了,通过这个工厂创建类来创建一个会话工厂。
2.SqlsessionFactory:从一个工厂类中得到一个会话对象,一个项目中只需要创建一个会话对象。通过会话工厂对象来创建会话对象。
3.Sqlsession:每次访问数据库都需要创建一个会话对象,这个会话对象不能共享。访问完成以后会话需要关闭。
package com.czy; import com.czy.dao.UserMapper; import com.czy.pojo.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 Test { @org.junit.Test public void test01() throws Exception { //1.从配置文件中获取SqlsessionFactory String resource = "mybatis-config.xml"; // //加载核心配置文件获取输入流 InputStream is= Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //2.从sqlsession中获取session SqlSession sqlSession = sqlSessionFactory.openSession(); //3.使用sqlsession获取获取接口的动态代理对象 !!!! UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.findAll(); for (User user : userList) { System.out.println(user); } //关闭会话 sqlSession.close(); } }
结果:
编写流程:Resources工具类直接可以读取src目录下配置文件,转成输入流。
mybatis配置文件分两种:
1.核心配置文件:mybatis-config.xml配置连接数据库参数
2.映射文件:UserMapper.xml编写sql语句
说明:
1.第一步:是从核心配置文件mybatis-config.xml中构建SqlSessionFactory对象,由于核心配置文件mybatis-config.xml中关联了映射文件UserMapper.xml,所以在SqlSessionFactory中也存在映射文件的内容
2.第二步:是从SqlSessionFactory中获取SqlSession会话对象,其实SqlSession会话对象底层封装的就是conn连接对象
3.第三步:是通过SqlSession会话对象调用查询方法selectList然后根据参数找到映射文件中中的sql语句并将数据封装到pojo的User对象中
可以在idea中定义模板。
<?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> <!--加载外部的配置文件--> <properties resource="db.properties"></properties> <!--settings--> <settings> <!--开启驼峰自动映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <!--别名--> <typeAliases> <package name="com.czy.sh.pojo"></package> </typeAliases> <!--mybatis环境的配置 一个核心配置文件,可以配置多个运行环境,default默认使用哪个运行环境 --> <environments default="development"> <!--通常我们只需要配置一个就可以了, id是环境的名字 --> <environment id="development"> <!--事务管理器:由JDBC来管理--> <!-- 事务管理器type的取值: 1. JDBC:由JDBC进行事务的管理 2. MANAGED:事务由容器来管理,后期学习Spring框架的时候,所有的事务由容器管理 --> <transactionManager type="JDBC"/> <!--数据源的配置:mybatis自带的连接池--> <!-- 数据源: 1. POOLED:使用mybatis创建的连接池 2. UNPOOLED:不使用连接池,每次自己创建连接 3. JNDI:由服务器提供连接池的资源,我们通过JNDI指定的名字去访问服务器中资源。 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--映射器--> <mappers> <!--加载其它的映射文件 注:注解开发是点号--> <!-- <package name="com.czy.sh.dao"></package>--> <!--加载其它的映射文件 注:不是点号--> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <!-- 加载其它的映射文件 xml形式 包扫描方式加载mapper映射文件,说明: 1. 要求mapper映射文件,与mapper接口要放在同一个目录 2. 要求mapper映射文件的名称,与mapper接口的名称要一致 --> <package name="com.czy.sh.dao"></package> </mappers> </configuration>
mybatis-config.xml,是MyBatis的全局配置文件,包含全局配置信息,如数据库连接参数、插件等。整个框架中只需要一个即可。
1.mybatis全局配置文件是mybatis框架的核心配置,整个框架只需一个;
2.mybatis全局配置文件的配置顺序,必须按照一下顺序进行配置:
properties:属性配置
settins:设置
typeAliases:类型别名设置
typeHandlers:类型处理器
enviroments:环境配置
environment:环境配置
transactionManager:事务管理器
dataSource:数据源
mappers:映射器
1.加载外部的java资源文件(properties文件,一般是数据源的配置);
2.通过子标签property设置属性
使用properties属性,配置数据库连接参数
1.通过property标签设置属性;
2.使用¥{key}获取设置的属性值
新建jdbc.properties文件,将连接参数抽取到配置文件中:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/heima
username=root
password=root
引入资源文件
【注意事项】:子标签和属性resource同时加载一个变量,先加载子标签,然后在加载resource关联的外部文件的值,那么子标签中的内容会被覆盖。此时子标签存在就没有意义了。
settings参数有很多,经常用到的是mapUnderscoreToCamelCase,就是映射下划线到驼峰命名。
开启驼峰匹配:完成经典的数据库命名到java属性的映射
经典数据库命名:如果多个单词,通常用下划线分割。
java:第二个单词大写。
驼峰匹配:相当于去掉数据库中的下划线和java进行匹配。
从数据库中查到数据的字段名user_name和实体类属性名userName,不一致,导致查到的数据无法封装到实体类中。
但是,实体类中的属性userName符合驼峰命名,数据库字段名user_name符合数据库经典字段命名。
如果此时直接查询数据,那么用户名是null。
1.配置驼峰匹配
在mybatis-config.xml中做如下配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
2.如果数据库字段名和属性名不一致,或者是也不满足上面的命名规则。可以通过在SQL语句中为字段名取别名进行映射。
<select id = "queryAll" resultType = "com.czy.pojo.User">
select *,user_name as userName from user
</select >
类型别名是给类的权限定名称取一个段名称。存在的意义就在于减少类完全限定名称的长度。
这些全限定类名可通过设置类型别名–短名称代替。类型别名的设置有以下几种方式:
扫面指定包下的所有类,扫描之后的别名就是类名,不区分大小写:
<!--
三、typeAliases(类型别名)
【1】作用:给类的全限定名称 取一个短名称 com.heima.mybatis.pojo.User==>User
【2】用法:
1、单独取别名:<typeAlias type="com.heima.mybatis.pojo.User" alias="User"/>
2、批量取别名:<package name="com.heima.mybatis.pojo"/> 扫描到当前包下的所有类
类的类名==》别名
-->
<typeAliases>
<!--扫描com.itheima.sh.pojo包下所有的类,类名直接作为别名(别名不区分大小写)-->
<package name="com.itheima.sh.pojo"/>
</typeAliases>
使用别名:
这是一些为常见的 Java 类型内建的相应的类型别名。它们都是不区分大小写的,注意对基本类型名称重复采取的特殊命名风格。
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
无论是mybatis在预处理语句(PreparedStatement) 中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成java类型,下表描述了一些默认的类型处理器
数据库数据类型:varchar ===StringTypeHandler===> 实体类:String
数据库数据类型:double ===DoubleTypeHandler===> 实体类中的数据: java.lang.Double
mybatis可以配置成适应多种环境,例如:开发、测试和成产需要有不同的配置:
第一步:在environments标签中配置多个environment,通过属性default指定一个默认环境配置:
第二步:在构建SqlSessionFactory时,可指定具体环境,如果不指定就使用默认的环境配置;
第一步:在environments中设置多个环境:
第二步:在构建SqlSessionFactory时,通过environment的id指定环境
虽然,这种方式也可以做到很方便的分离多个环境,但是实际使用场景下,我们更多的是选择使用第三方的连接池:druid,C3P0。并且使用spring来管理数据源(连接池),来做到环境的分离。
mappers: UserMapper.xml =====>UserMapper.java接口关联
作用:维护接口和映射文件之间的关系。
在mybatis-config.xml文件中,通过mapper标签的resource属性引入当前工程src下的映射文件:
缺点:有多个映射文件就是配置多个mapper,很麻烦。
在mybatis-config.xml文件中开启包扫描:扫描包下的所有接口和对应的映射文件。
条件:
1.接口名和映射文件名保持一致
2.路径保持一致
在mybatis-config.xml配置mapper接口的全路径:
<mappers>
<package name="com.itheima.sh.dao"/>
</mappers>
扫描目标目录下的mapper接口,并按照class的方式找到接口对应的映射文件
要求:
1.映射文件和mapper接口在同一目录下
2.文件名必须一致
3.映射文件的namespace必须和mapper接口的全路径保持一致
1、需要遵守的条件太多
2、mapper.xml和mapper.java没有分离。
package com.czy.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 SqlSessionUtil { private static SqlSessionFactory sqlSessionFactory; static { //实例化工厂建造类 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); //读取核心配置文件 try { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); //创建工厂对象 sqlSessionFactory = sqlSessionFactoryBuilder.build(is); } catch (IOException e) { e.printStackTrace(); } } /** * 获取会话对象,自动提交事务 * @return */ public static SqlSession getSession(){ return sqlSessionFactory.openSession(true); } /** * 是否自动提交事务 * @param flag * @return */ public static SqlSession getSession(Boolean flag){ return sqlSessionFactory.openSession(flag); } /** * 提交事务并关闭session */ public static void commitAndClose(SqlSession session){ if (session!=null){ session.commit(); session.close(); } } /** * 回滚事务,并关闭session */ public static void rollBackAndClose(SqlSession session){ if (session!=null){ session.rollback(); session.close(); } } }
测试:
public class Test {
@org.junit.Test
public void test01() throws Exception {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
SqlSessionUtil.commitAndClose(session);
}
}
mapper映射文件中定义了操作数据库的sql,每一个sql都被包含在一个statement中。映射文件是mybatis操作数据库的核心。
映射文件需要直接书写sql语句对数据库进行操作,对数据库操作sql语句主要有CRUD这四类,这四类对应到映射文件中的配置为四类标签:select,insert,update,delete
public interface UserMapper {
List<User> findAll();
User findById(int id);
void save(User user);
void update(User user);
void delete(int id);
}
Select标签:用来编写查询语句的statement
UserMapper.xml:
<!--根据id查询用户数据-->
<!--parameterType="int" 表示sql语句参数id的类型,int是Integer的别名-->
<select id="queryById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
Insert标签:编写新增语句的statement
<!--新增
注意:#{username},#{birthday},#{sex},#{address} 大括号里面的值必须和pojo的实体类User类中的属性名一致,否则会报错。其实这里看的是User类中的getXxx()的get后面的xxx内容
例如 getUserName---看的是userName ,就是将get去掉,U变为小写u,后面不变
-->
<insert id = "save">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
</insert>
Update标签:编写更新语句的statement
<!--
username = #{username}
1.等号左边表示数据表列名
2.#{username} 这里的username需要和pojo中的get和set方法后面的标识符一致
-->
<update id="updateUser">
update user set username = #{username},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}
</update>
Delete标签:
<delete id = "delete">
delete from user where id =#{id}
</delete>
CRUD标签都有一个属性parameterType,底层的statement通过它指定接收的参数类型,入参数据有以下几种类型:hashMap,基本数据类型(包装类),实体类;
在mybatis中入参的数据类型分为2种:
1.简单数据类型:int,String,long,date
2.复杂数据类型:类和map
说明:如果传递参数是数组或者集合,底层都会封装到Map集合中。
【基本类型数据】
<!--根据id查询-->
<!--parameterType="int" 表示sql语句参数id的类型,int是Integer的别名.MyBatis 可以通过类型处理器(TypeHandler) 根据接口中的方法User queryById(Integer id)参数类型推断出具体传入语句的参数类型。-->
<select id="queryById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
【pojo类型】
<insert id="savetUser" parameterType="User">
INSERT INTO user(...)
</insert>
说明:对于parameterType属性可以不写,如果不写,mybatis会通过TypeHandler根据接口中的方法参数类型推断出具体传入语句的参数类型
需求
新增一条数据,将这条数据的主键封装到实体类中,并查看主键的值。
测试类代码:
@Test
public void saveUser() throws Exception {
User user = new User();
user.setUsername("蔡徐坤");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("上海");
userMapper.saveUser(user);
//查看新的数据的主键值
System.out.println(user.getId());//null
}
直接获取的结果是null。
有两种方式可以实现添加好数据之间将主键封装到实体类对象中:
mysql中的函数:last_insert_id() 得到最后添加的主键
<insert id="save" parameter="user">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
<!--
keyColumn:主键在表中对应的列名
keyProperty:主键在实体类中对应的属性名
resultType:主键的数据类型
order:
BEFORE: 在添加语句前执行查询主键的语句
AFTER: 在添加语句后执行查询主键的语句
-->
<selectKey keyColumn = "id" keyProperty ="id" resultType = "int" order = "AFTER">
select last_insert_id();
</selectKey>
</insert>
<!-- useGeneratedKeys :true 获取自动生成的主键,相当于select last_insert_id()
keyColumn:表中主键的列名
keyProperty:实体类中主键的属性名
-->
<insert id="save2" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
</insert>
主要针对简单类型的数据(int,string,long,date)等数据进行入参处理。
接口方法传入一个参数
User queryById(Integer id);
<!--根据id查询-->
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{id}
</select>
<!--根据id查询-->
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{abc}
</select>
当接口方法传入一个参数时,mybatis不做特殊处理,只需要#{任意变量名}即可
需求:根据用户名和性别查询用户
//根据用户名和性别查询
User queryByUserNameAndSex(String userName, String sex);
###userMapper.xml
<select id="queryByUserNameAndSex" resultType="User">
select * from user where username=#{username} and sex=#{sex}
</select>
@Test
public void queryByUserNameAndSex() throws Exception {
User user = userMapper.queryByUserNameAndSex("孙悟空", "男");
System.out.println("user = " + user);
}
当传入多个参数时,mybatis底层进行了处理,我们需要按照一下几种方式解决异常:
<select id ="queryByUserNameAndSex" resultType = "user">
select * from user where username = #{arg0} and sex = #{arg1}
</select>
说明:
这里的sql语句的参数书写顺序:select * from user where username=#{arg0} and sex=#{arg1}
和User user = userMapper.queryByUserNameAndSex(“孙悟空”, “男”); 传递实参顺序一致。
也就是说:username=#{arg0} —》arg0的值是姓名孙悟空。
sex=#{arg1}—》arg1的值是性别男。
<!--根据用户名和性别查询-->
<select id="queryByUserNameAndSex" resultType="User">
select * from user where username=#{param1} and sex=#{param2}
</select>
步骤一:在接口中传入参数时通过@Param指定参数名称
User queryByUserNameAndSex(@Param("username") String username,@Param("sex") String sex);
步骤二:在接收参数时,通过指定的名称获取参数值
<!--根据用户名和性别查询-->
<select id="queryByUserNameAndSex" resultType="User">
<!--#{username}这里的username是@Param("username"),也就是说@Param("标识符")标识符是什么,这里就写什么-->
select * from user where username=#{username} and sex=#{sex}
</select>
使用pojo来传递参数
void saveUser(User user);
<!--新增-->
<insert id="saveUser">
insert into user values (null ,#{username},#{birthday},#{sex},#{address})
</insert>
说明:接口方法传入pojo类型的数据时,mybatis底层直接使用pojo封装数据,sql语句中#{username}取值就是到pojo中调用getUsername();
需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型分别表示用户名和性别。
在UserMapper接口中添加以下方法:
/**
* 用户登陆,参数为map
* @return
*/
User login(Map<String,String> map);
UserMapper.xml配置文件:
<!--
将map的key作为参数名称来传递参数
-->
<select id="login" resultType="User">
select * from user where username=#{username} and sex=#{sex}
</select>
参数值的获取指的是,statement获取接口方法中传入的参数,获取参数,有两种方式:#{}和${}
#{}取值:
mybatis后台处理:
${}取值:
mybits后台处理:
注意:${id} 获取id值时,必须使用命名参数取值:
1.sql语句中获取参数的方式:
#{}: select * from user where id = ?
${}: select * from user where id = 1
2. 取值的相同点:
都能获取接口方法传入的参数值
3.取值的不同点:
#{}取值:是以预编译的形式将参数设置到sql语句中。防止sql的注入问题
${}取值:直接把获取到的参数值,拼接到sql语句中,会有安全问题,不能防止sql注入
$()取值的应用场景:
1、企业开发中随着数据量的增大,往往会将数据表按照年份进行分表,如:2017_user,2018_user....,对这些表进行查询就需要动态把年份传入进来,而年份是表名的一部分,并不是参数,JDBC无法对其预编译,所以只能使用${}进行拼接:
SELECT * FROM ${year}_user;
2、根据表名查询数据总记录数:
SELECT COUNT(*) FROM user
SELECT COUNT(*) FROM order
SELECT COUNT(*) FROM ${tableName}
简言之:如果需要设置到SQL中的不是查询的条件,只能使用${}拼接;
在使用原生的JDBC操作时,对于结果集resultSet,需要手动处理,mybatis框架提供了resultType和resultMap来对结果集进行封装。
注意:只要一个方法有返回值需要处理,那么resultType和resultMap必须有一个。
从sql语句中返回期望类型的类的完全限定名或别名。如果返回的是集合,那么应该设置集合中包含的类型,而不是集合本身。可以使用resultType或resultMap,但不能同时使用。
1.返回一条数据,封装到map中
需求:
查询id是1的数据,将查询的数据结果封装到Map<String,Object>中
接口:
//需求:查询id是1的数据,将查询的结果封装到Map<String,Object>中
Map<String,Object> selectByIdReturnMap(Integer id);
sql:
<select id="selectByIdReturnMap" resultType="map">
select * from user where id=#{id}
</select>
结果:
map = {birthday=1980-10-24, address=花果山水帘洞, sex=男, id=1, username=孙悟空}
通过上面的测试,我们可以看到,如果返回一条数据放到Map中,那么列明会作为Map集合中的key,结果作为map中的value。
需求:
查询数据表所有的数据封装到Map<String,User>集合中
要求key值为一条记录的主键,value值为pojo对象
接口:在接口上面通过@MapKey指定key值封装到列数据
@MapKey("id")
Map<Integer,Object> selectUserByIdReturnMap2();
sql
<select id="selectUserByIdReturnMap2" resultType="map">
select * from user
</select>
结果:
{
1={birthday=1980-10-24, address=花果山水帘洞, sex=男, id=1, username=孙悟空},
2={birthday=1992-11-12, address=白虎岭白骨洞, sex=女, id=2, username=白骨精},
3={birthday=1983-05-20, address=福临山云栈洞, sex=男, id=3, username=猪八戒},
4={birthday=1995-03-22, address=盤丝洞, sex=女, id=4, username=蜘蛛精},
5={birthday=2022-06-14, address=河南, sex=男, id=5, username=张三},
6={birthday=2022-06-14, address=河南, sex=女, id=6, username=李四},
7={birthday=2022-06-14, address=河南, sex=女, id=7, username=王五}
}
ResultMap是mybatis中最重要最强大的元素,使用ResultMap可以解决两大问题:
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,而POJO中的属性名字是userName,两端不一致,造成mybatis无法填充对应的字段信息。
<select id="queryById" resultType="user" parameterType="int">
select *,name as username from user where id = #{id}
</select>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
注意:这种方式只能解决列名是下划线命名。
步骤一:将驼峰匹配注释掉
步骤二:配置resultMap
在映射文件中自定义结果集类型:
<!--type 表示结果集封装的类型
autoMapping属性的值:
为true时:在字段和属性名称相同时,会进行自动映射。如果不配置,则默认为true。
为false时:只针对resultMap中已经配置的字段作映射。
-->
<resultMap id="userResultMap" type="user" autoMapping="true">
<!--配置主键映射关系-->
<id column="id" property="id"></id>
<!--配置用户名的映射关系 column 表示数据表列 property表示pojo的属性-->
<result column="username" property="username" ></result>
</resultMap>
步骤三:修改查询语句
<select id="findAll" resultMap="userResultMap">
select * from user
</select>
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
例如:
需求:查询男性用户,如果输入了用户名,按用户名模糊查询,如果没有输入用户名,就查询所有男性用户。
正常的sql语句:查询男性并且用户名中包含zhang
select * from tb_user where sex = "男" and user_name like '%zhang%'
或
select * from tb_user where sex = "男"
实现需求时还要判断用户是否输入用户名来做不同的查询要求,而这里似乎没有办法判断是否输入了用户名,因此可以考虑使用动态sql来完成这个功能。
格式:
<if test="判断条件">
满足条件执行的代码
</if>
说明:
1)if标签:判断语句,用于进行逻辑判断的。如果判断条件为true,则执行if标签的文本内容
2)test属性:用来编写表达式,支持ognl;
接口:
/**
* 根据用户名模糊查询
* @param userName
* @return
*/
List<User> queryLikeUserName(@Param("userName") String userName);
sql:
<select id="queryLikeUserName" resultType="user">
select * from user where sex='男'
<if test="userName!=null and userName.trim()!=''">
and username like '%${userName}%'
</if>
</select>
注意:<if>
判断中:
1、if标签:用来判断;
2、test属性:使用OGNL表达式,完成具体的判断业务逻辑;
3、这里使用的字符串拼接,所以这里不能是#取值,只能使用$取值,否则会报错
测试:
有名字
没有名字
choose标签:分支选择(多选一,遇到成立的条件即停止)
when子标签:编写条件,不管有多少个when条件,一旦其中一个条件成立,后面的when条件都不执行。
test属性:编写ognl表达式
otherwise子标签:当所有条件都不满足时,才会执行该条件
需求:
编写一个查询方法,设置两个参数,一个是用户名,一个是住址。
根据用户名或者住址查询所有男性用户:
如果输入了用户名则按照用户名模糊查找,
否则就按照住址查找,两个条件只能成立一个,
如果都不输入就查找用户名为“孙悟空”的用户。
接口:
/*
查询用户名或者地址
*/
List<User> queryByUserNameOrAddress(@Param("userName") String userName, @Param("address") String address);
sql
<select id="queryByUserNameOrAddress" resultType="user">
select * from user where sex='男'
<choose>
<when test="userName !=null and userName.trim() != ''">
and username like '${userName}%'
</when>
<when test="address !=null and address.trim() != ''">
and address = #{address}
</when>
<otherwise>
and username = '孙悟空'
</otherwise>
</choose>
</select>
where标签:拼接多条件查询时 1、能够添加where关键字; 2、能够去除多余的and或者or关键字
案例:按照如下条件查询所有用户,
如果输入了用户名按照用户名进行查询,
如果输入住址,按住址进行查询,
如果两者都输入,两个条件都要成立。
接口
List<User> queryByUserNameAndAge(@Param("userName") String userName, @Param("address") String address);
sql
<select id="queryByUserNameAndAge" resultType="user">
SELECT * FROM user
<where>
<if test="userName != null and userName.trim()!=''">
username = #{userName}
</if>
<if test="address!=null and address.trim()!=''">
AND address = #{address}
</if>
</where>
</select>
set标签:在update语句中,可以自动添加一个set关键字,并且会将动态sql最后多余的逗号去除。
案例:修改用户信息,如果参数user中的某个属性为null,则不修改。
如果在正常编写更新语句时,如下:
update user SET username = ?, birthday=?, sex=?, where id = ?
那么一旦在传递的参数中没有address,此时生成的sql语句就会因为多了一个逗号而报错。
接口
void updateSelectiveUser(User user);
sql
<!--选择性地对user数据进行修改--> <update id="updateSelectiveUser"> update user <set> <if test="username != null and username.trim()!=''"> username = #{username}, </if> <if test="birthday != null"> birthday=#{birthday}, </if> <if test="sex != null and sex.trim()!=''"> sex=#{sex}, </if> <if test="address != null and address.trim()!=''"> address=#{address} </if> </set> where id = #{id} </update>
@Test
public void updateSelectiveUser() {
User user = new User();
user.setUsername("锁哥1");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("");
user.setId(7);
userMapper.updateSelectiveUser(user);
}
结果
update user SET username = ?, birthday=?, sex=? where id = ?
foreach标签:遍历集合或者数组
<foreach collection="集合名或者数组名" item="元素" separator="标签分隔符" open="以什么开始" close="以什么结束">
#{元素}
</foreach>
collection属性:接收的集合或者数组,集合名或者数组名
item属性:集合或者数组参数中的每一个元素
separator属性:标签分隔符
open属性:以什么开始
close属性:以什么结束
需求:按照id值是1,2,3来查询用户数据;
接口:
List<User> queryByIds(@Param("arrIds") Integer[] arrIds);
这里一定加@Param(“arrIds”),否则报错
sql
<!--根据多个id值查询-->
<select id="queryByIds" resultType="user">
SELECT * FROM user WHERE id IN
<foreach collection="arrIds" item="ID" separator="," open="(" close=")">
#{ID}
</foreach>
</select>
sql准备:
/* Navicat MySQL Data Transfer Source Server : localhost Source Server Version : 50622 Source Host : localhost:3306 Source Database : heima81 Target Server Type : MYSQL Target Server Version : 50622 File Encoding : 65001 Date: 2019-08-20 17:08:12 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for tb_item -- ---------------------------- DROP TABLE IF EXISTS `tb_item`; CREATE TABLE `tb_item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `item_name` varchar(32) NOT NULL COMMENT '商品名称', `item_price` float(6,1) NOT NULL COMMENT '商品价格', `item_detail` text COMMENT '商品描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of tb_item -- ---------------------------- INSERT INTO `tb_item` VALUES ('1', 'iPhone 6', '5288.0', '苹果公司新发布的手机产品。'); INSERT INTO `tb_item` VALUES ('2', 'iPhone 6 plus', '6288.0', '苹果公司发布的新大屏手机。'); -- ---------------------------- -- Table structure for tb_order -- ---------------------------- DROP TABLE IF EXISTS `tb_order`; CREATE TABLE `tb_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `order_number` varchar(20) NOT NULL COMMENT '订单号', PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`), CONSTRAINT `FK_orders_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of tb_order -- ---------------------------- INSERT INTO `tb_order` VALUES ('1', '1', '20140921001'); INSERT INTO `tb_order` VALUES ('2', '2', '20140921002'); INSERT INTO `tb_order` VALUES ('3', '1', '20140921003'); -- ---------------------------- -- Table structure for tb_orderdetail -- ---------------------------- DROP TABLE IF EXISTS `tb_orderdetail`; CREATE TABLE `tb_orderdetail` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_id` int(32) DEFAULT NULL COMMENT '订单号', `item_id` int(32) DEFAULT NULL COMMENT '商品id', `total_price` double(20,0) DEFAULT NULL COMMENT '商品总价', `status` int(11) DEFAULT NULL COMMENT '状态', PRIMARY KEY (`id`), KEY `FK_orderdetail_1` (`order_id`), KEY `FK_orderdetail_2` (`item_id`), CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`order_id`) REFERENCES `tb_order` (`id`), CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`item_id`) REFERENCES `tb_item` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of tb_orderdetail -- ---------------------------- INSERT INTO `tb_orderdetail` VALUES ('1', '1', '1', '5288', '1'); INSERT INTO `tb_orderdetail` VALUES ('2', '1', '2', '6288', '1'); INSERT INTO `tb_orderdetail` VALUES ('3', '2', '2', '6288', '1'); INSERT INTO `tb_orderdetail` VALUES ('4', '3', '1', '5288', '1'); -- ---------------------------- -- Table structure for tb_user -- ---------------------------- DROP TABLE IF EXISTS `tb_user`; CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_name` varchar(100) DEFAULT NULL COMMENT '用户名', `password` varchar(100) DEFAULT NULL COMMENT '密码', `name` varchar(100) DEFAULT NULL COMMENT '姓名', `age` int(10) DEFAULT NULL COMMENT '年龄', `sex` int(11) DEFAULT NULL COMMENT '0-女 1-男', PRIMARY KEY (`id`), UNIQUE KEY `username` (`user_name`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of tb_user -- ---------------------------- INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '30', '1'); INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '21', '0'); INSERT INTO `tb_user` VALUES ('3', 'wangwu', '123456', '王五', '22', '1'); INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1'); INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '0'); INSERT INTO `tb_user` VALUES ('6', '蔡徐坤', '123', '小菜', '18', '1');
实体类
Item:
package com.czy.pojo; public class Item { private int id; private String itemName; private double itemPrice; private String itemDetail; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getItemName() { return itemName; } public void setItemName(String itemName) { this.itemName = itemName; } public double getItemPrice() { return itemPrice; } public void setItemPrice(double itemPrice) { this.itemPrice = itemPrice; } public String getItemDetail() { return itemDetail; } public void setItemDetail(String itemDetail) { this.itemDetail = itemDetail; } @Override public String toString() { return "Item{" + "id=" + id + ", itemName='" + itemName + '\'' + ", itemPrice=" + itemPrice + ", itemDetail='" + itemDetail + '\'' + '}'; } }
order
package com.czy.pojo; public class Order { private Integer id; private String orderNumber; //关联User对象 private User user; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getOrderNumber() { return orderNumber; } public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Order{" + "id=" + id + ", orderNumber='" + orderNumber + '\'' + ", user=" + user + '}'; } }
orderDetail
package com.czy.pojo; public class OrderDetail { private int id; private int orderId; private int itemId; private double totalPrice; private String status; public int getId() { return id; } public void setId(int id) { this.id = id; } public int getOrderId() { return orderId; } public void setOrderId(int orderId) { this.orderId = orderId; } public int getItemId() { return itemId; } public void setItemId(int itemId) { this.itemId = itemId; } public double getTotalPrice() { return totalPrice; } public void setTotalPrice(double totalPrice) { this.totalPrice = totalPrice; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "OrderDetail{" + "id=" + id + ", orderId=" + orderId + ", itemId=" + itemId + ", totalPrice=" + totalPrice + ", status='" + status + '\'' + '}'; } }
user
package com.czy.pojo; import java.io.Serializable; import java.util.Date; import java.util.List; public class User implements Serializable { private Long id; // 用户名 private String userName; // 密码 private String password; // 姓名 private String name; // 年龄 private Integer age; //0 女性 1 男性 private Integer sex; //订单 List<Order> orders; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getSex() { return sex; } public void setSex(Integer sex) { this.sex = sex; } public List<Order> getOrders() { return orders; } public void setOrders(List<Order> orders) { this.orders = orders; } @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + ", password='" + password + '\'' + ", name='" + name + '\'' + ", age=" + age + ", sex=" + sex + ", orders=" + orders + '}'; } }
测试接口:
public interface UserMapper {
//完成根据id查询用户数据;
User selectById(Long id);
}
sql:
<!--根据id查询:statement-->
<select id="selectById" resultType="User">
SELECT * FROM tb_user WHERE id=#{id}
</select>
1.tb_user和 tb_order表关系
tb_user 《==》 tb_order:一对多, 一个人可以下多个订单
tb_order 《==》 tb_user:一对一,一个订单只能属于一个人
结论:tb_user和tb_order属于一对多的关系,需要将一方tb_user的主键作为多方tb_order的外键维护关系
2.tb_order 和 tb_item 表关系
tb_order 《==》 tb_item :一个订单可以有多个商品
tb_item 《==》 tb_order:一个商品可以在多个订单上
结论:tb_order和tb_item属于多对多的关系,需要创建中间表tb_orderdetail维护两个表的关系,并且将两张表 的主键作为中间表的外键
需求:通过订单编号20140921003查询出订单信息,并查询出下单人信息。
订单接口:
<select id="queryOrderAndUserByOrderNumber2" resultType="order">
select * from tb_order as order inner join tb_user as user on order.user_id = user.id where user.id = #{orderNumber}
</select>
注意:需要在order实体类中添加User属性
mybatis-config.xml配置文件中将orderMapper.xml添加到映射器中
sql:
<?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"> <!-- 映射文件 namespace 指定接口的类全名 --> <mapper namespace="com.itheima.sh.dao.OrderMapper"> <!-- 1.autoMapping="true" 表示只需要给当前表的id然后自动映射当前表的其他列值到 对应实体类的属性中,这属于偷懒行为,开发中我们最好都书写出来 2.id标签表示id的映射关系 3.result标签表示其他列和pojo类的属性映射关系 4.一对一映射关系使用子标签association来表示引用的另一个pojo类的对象 --> <resultMap id="orderAndUserResultRelative" type="Order" autoMapping="true"> <!--主表主键--> <id column="id" property="id"/> <!--关联关系--> <!-- 1.property="user" 表示在Order类中的引用的User类的对象成员变量名 2.javaType="User" 表示引用的user对象属于User类型 --> <association property="user" javaType="User" autoMapping="true"> <!--从表主键--> <id column="id" property="id"/> <!--<result column="user_name" property="userName"/>--> </association> </resultMap> <!--多表关联查询:一对一--> <select id="queryOrderAndUserByOrderNumber2" resultMap="orderAndUserResultRelative"> SELECT * FROM tb_order tbo INNER JOIN tb_user tbu ON tbo.user_id = tbu.id WHERE tbo.order_number = #{orderNumber} </select> </mapper>
结果
注意:
因为tb_user表的主键是id,tb_order的主键也是id。查询的结果中有两列相同的id字段。在将查询结果封装到实体类的过程中就会封装错误。
解决方案:
1、建议将所要查询的所有字段显示地写出来;
2、将多表关联查询结果中,相同的字段名取不同的别名;
resultMap中应该如下配置:
需求:查询id为1的用户及其订单信息
分析:
一个用户可以有多个订单
用户(1) ---- 订单(n)
需要在user实体类中添加集合型的订单
接口:
/**
* 根据用户id查询用户及其订单信息
* @param id
* @return
*/
User oneToManyQuery(@Param("id") Long id);
sql
<!--自定义结果集--> <resultMap id="oneToManyResult" type="User" autoMapping="true"> <!--User的主键--> <id column="uid" property="id"/> <!--Order关联映射--> <!-- 1.一对多使用collection子标签进行关联多方Order 2.属性: 1)property="orders" 这里的orders表示User类的成员变量orders 2)javaType="List" 表示User类的成员变量orders存储的Order对象使用的类型,这里是List,可以不配置 3) ofType="Order" 表示List集合中存储数据的类型 Order --> <collection property="orders" javaType="List" ofType="Order" autoMapping="true"> <!--Order的主键--> <id column="oid" property="id" /> </collection> </resultMap> <!--根据用户ID查询用户及其订单数据--> <select id="oneToManyQuery" resultMap="oneToManyResult"> SELECT tbo.id as oid, tbo.order_number, tbu.id as uid, tbu.user_name, tbu.password, tbu.name, tbu.age, tbu.sex FROM tb_user tbu INNER JOIN tb_order tbo ON tbu.id = tbo.user_id WHERE tbu.id = #{id} </select>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。