思维导图:
1.入门
1.1框架
框架是软件开发的一套解决方案,不同的解决方案解决不同的问题,框架封装了很多细节,使开发者们以极简的方式实现功能,大大提高了开发效率,他是一个半成品,有各种组件,选用某种组件完成系统实现。
1.2持久层技术解决方案
JDBC技术(规范),其中spring的jdbcTemplete是spring对jdbc的简单封装,Apache的DBUtil是Apache对JDBC的简单封装,都属于帮助类,并不是框架,我们还是需要写很多东西。
1.3mybatis概述
是一个持久层框架,用java语言写的,封装了jdbc的细节,开发者只需要关注sql语句,而无需关注,注册驱动,创建连接等过程。它使用ORM(Object Relation Mapping)思想实现了对结果集的封装,ORM是对象关系映射,将数据库表和实现类对应起来,是我们操作实体类就可以操作数据库
2.搭建mybatis项目
2.1创建maven工程
注意 :将maven仓库改成本地的
2.2创建实体类
2.3导入坐标
<!--以jar的方式-->
<packaging>jar</packaging>
<dependencies>
<!--导入mybatis坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!--导入mysql坐标-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<!--导入junit坐标-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
<!--找到配置文件插件-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
2.4创建持久层接口和方法
2.5创建主配置文件
<?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>
<environments default="development">
<environment id="development">
<!--transactionManager用来配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--jdbc配置信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--配置文件的映射 resource为xml方式,class为注解方式-->
<mapper resource="com/test/dao/IUserMapper.xml"></mapper>
</mappers>
</configuration>
2.6创建mapper文件
<?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为接口的全限定类名-->
<mapper namespace="com.test.dao.IUserMapper">
<!--id对应的是持久层接口中的方法名,必须要加上resultType属性作为返回之类型-->
<select id="findAll" resultType="com.test.domain.User">
select id,username,birthday,sex,address from user;
</select>
</mapper>
2.7创建测试类
//定义xml文件字符串
String resource = "SqlMapConfig.xml";
//1.加载配置文件
InputStream in = Resources.getResourceAsStream(resource);
//2.创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(in);
//3.打开工厂会话,创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.通过SqlSession对象创建代理对象
IUserMapper userMapper = SqlSession.getMapper(IUserMapper.class);
//5.调用方法
List<User> userList = userMapper.findAll();
for(User user : userList){
System.out.println(user);
}
//6.关闭资源
sqlSession.close();
in.close();
2.8环境搭建的注意事项
1.在mybatis中把持久层和操作接口名称和映射文件叫做mapper,所以IUerDao和IUserMapper一样
2.mapper配置文件的结构要和持久层接口的包结构相同
3.映射文件的配置文件中的mapper标签中namespqce属性值必须是持久层接口的全限定类名
4.映射配置文件的操作配置,id属性的取值必须是dao接口的方法名。且必须有返回值类型resultType=“对应实体类的全限定类名”
5.在使用注解时要将mapper配置文件删掉,才不会报错
3.自定义mybaits
mybatis在使用代理dao的方式实现增删改查时做了什么事?
一.创建代理对象
二.在代理对象中调用selectList方法
自定义mybatis能通过入门案例看到的类
class Resources
class SqlSessionFactoryBuilder
interface SqlSessionFactory
interface SqlSession
3.1步骤一:创建基本工程添加依赖等,但是不添加mybatis依赖
3.2步骤二:创建mybatis.io.Resources类
package com.test.mybatis.io;
import java.io.InputStream;
/**
* TODO:使用类加载器读取配置文件的类
*
* @author blp
* @date 2020/8/13 18:47
*/
public class Resources {
/**
* 根据传入的参数获取一个字节输入流
*
* @param filePath
* @return
*/
public static InputStream getResourceAsStream(String filePath) {
//下面这句执行了三步,第一步获取当前类的字节码,第二步,获取字节码的类加载器,第三步根据类加载器读取配置
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
}
3.3步骤三:创建SqlSessionFactoryBuilder类,并编写build方法
package com.test.mybatis.sqlsession;
import java.io.InputStream;
/**
* TODO:用于创建一个sqlsessionfactory对象
*
* @author blp
* @date 2020/8/13 18:53
*/
public class SqlSessionFactoryBuilder {
public SqlsessionFactory build(InputStream config){
return null;
}
}
3.4步骤四:创建sqlsessionFactory接口
package com.test.mybatis.sqlsession;
/**
* TODO:
*
* @author blp
* @date 2020/8/13 18:56
*/
public interface SqlSessionFactory {
/**
* 用于打开一个新的sqlSession对象
* @return
*/
SqlSession openSession();
}
3.5步骤五:创建sqlsession接口
package com.test.mybatis.sqlsession;
/**
* TODO:自定义mybatis中和数据库交互的核心类,他里面可以创建dao接口的代理对象
*
* @author blp
* @date 2020/8/13 18:59
*/
public interface SqlSession {
/**
* 根据参数创建一个代理对象
* @param daoInterfaceClass dao的接口字节码而不是是实现类
* @param <T>
* @return
*/
<T> T getMapper(Class<T> daoInterfaceClass);
/**
* 资源释放
*/
void close();
}
3.6步骤六:删除配置文件中的mybaits url约束,创建一个包utils
然后在包中添加解析xml的工具类,使用的是dom4j和xpath技术
需要导的依赖有
<!--导入dom4j坐标-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<!--导入xpath坐标-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
3.7步骤七:创建解析xml工具类
package com.test.mybatis.utils;
import com.test.cfg.Configuration;
import com.test.cfg.Mapper;
import com.test.mybatis.io.Resources;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* TODO
*
* @author blp
* @date 2020/8/13 19:07
*/
public class XMLConfigBuilder {
public static Configuration loadConfiguration(InputStream config){
Configuration cfg=new Configuration();
try {
SAXReader saxReader=new SAXReader();
Document document=saxReader.read(config);
Element root=document.getRootElement();
List<Element> propertyElements=root.selectNodes("//property");
for(Element element:propertyElements){
String name=element.attributeValue("name");
if("driver".equals(name)){
String driver=element.attributeValue("value");
cfg.setDriver(driver);
}
if("url".equals(name)){
String url=element.attributeValue("value");
cfg.setUrl(url);
}
if("username".equals(name)){
String username=element.attributeValue("value");
cfg.setUsername(username);
}
if("password".equals(name)) {
String password = element.attributeValue("value");
cfg.setPassword(password);
}
}
List<Element> mapperElements=root.selectNodes("//mappers/mapper");
for(Element mapperElement:mapperElements){
Attribute attribute=mapperElement.attribute("resource");
if(attribute!=null){
System.out.println("这是xml解析");
String mapperPath=attribute.getValue();
Map<String,Mapper> mappers=loadMapperConfiguration(mapperPath);
cfg.setMappers(mappers);
}else{
// System.out.println("这是注解实现");
// String daoClassPath=mapperElement.attributeValue("class");
// Map<String,Mapper> mappers=loadMapperAnnoation(daoClassPath);
// cfg.setMappers(mappers);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return cfg;
}
private static Map<String,Mapper> loadMapperConfiguration(String mapperPath){
System.out.println("132131312");
Map<String,Mapper> mappers=new HashMap<String,Mapper>();
try {
InputStream in= Resources.getResourceAsStream(mapperPath);
SAXReader reader=new SAXReader();
System.out.println(in);
Document document=reader.read(in);
Element root=document.getRootElement();
System.out.println(root);
String namespace=root.attributeValue("namespace");
System.out.println(namespace);
List<Element> selsctElements=root.selectNodes("//select");
System.out.println(selsctElements.size());
for(Element selectElement:selsctElements){
String id=selectElement.attributeValue("id");
String resultType=selectElement.attributeValue("resultType");
String queryString=selectElement.getText();
String key=namespace+"."+id;
Mapper mapper=new Mapper();
mapper.setQueryString(queryString);
mapper.setResultType(resultType);
mappers.put(key,mapper);
}
} catch (DocumentException e) {
e.printStackTrace();
}
return mappers;
}
}
3.8步骤八:创建自定义配置类和Mapper类(用于封装执行的sql语句和接口类型的全限定类名)
Configuration类
package com.test.cfg;
import java.util.HashMap;
import java.util.Map;
/**
* TODO:自定义mybatis的配置类
*
* @author blp
* @date 2020/8/13 19:25
*/
public class Configuration {
private String driver;
private String url;
private String username;
private String password;
private Map<String,Mapper> mappers=new HashMap<String, Mapper>();
public Map<String, Mapper> getMappers() {
return mappers;
}
public void setMappers(Map<String, Mapper> mappers) {
this.mappers.putAll(mappers);//此处需要使用追加的方式
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
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;
}
}
Mapper类
package com.test.cfg;
/**
* TODO:用于封装执行的sql语句和结果类型的全限定类名
* String
*
* @author blp
* @date 2020/8/13 19:36
*/
public class Mapper {
//Sql语句
private String queryString;
//实体类的全限定类名
private String resultType;
public String getQueryString() {
return queryString;
}
public void setQueryString(String queryString) {
this.queryString = queryString;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
}
3.9步骤九:创建sqlsessionfactory的实现类
package com.test.mybatis.sqlsession;
import com.test.cfg.Configuration;
/**
* TODO:SqlSessionFactory接口的是现类
*
* @author blp
* @date 2020/8/13 21:04
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration cfg;
public DefaultSqlSessionFactory(Configuration cfg){
this.cfg=cfg;
}
/**
* 用于创建一个新的数据库操作对象
* @return
*/
public SqlSession openSession(){
return new DefaultSqlSession(cfg);
}
}
3.10步骤十:创建sqlsession接口的实现类
package com.test.mybatis.sqlsession;
import com.test.cfg.Configuration;
import com.test.mybatis.sqlsession.proxy.MapperProxy;
import com.test.mybatis.utils.DataSourceUtil;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
/**
* TODO:SqlSession的实现类
*
* @author blp
* @date 2020/8/13 21:09
*/
public class DefaultSqlSession implements SqlSession {
private Configuration cfg;
private Connection connection;
public DefaultSqlSession(Configuration cfg) {
this.cfg = cfg;
connection = DataSourceUtil.getConnection(cfg);
}
/**
* 用于创建代理对象
*
* @param daoInterfaceClass dao的接口字节码而不是是实现类
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{
daoInterfaceClass}, new MapperProxy(cfg.getMappers(), connection));
}
/**
* 用于释放资源
*/
public void close() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3.11步骤十一:创建连接的工具类
package com.test.mybatis.utils;
import com.test.cfg.Configuration;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* TODO:用于创建数据源的工具类
*
* @author blp
* @date 2020/8/13 21:44
*/
public class DataSourceUtil {
/**
* 用于获取一个链接
* @param cfg
* @return
*/
public static Connection getConnection(Configuration cfg){
try {
Class.forName(cfg.getDriver());
return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
3.12步骤十二:创建代理对象的类
package com.test.mybatis.sqlsession.proxy;
import com.test.cfg.Mapper;
import com.test.mybatis.utils.Executor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Map;
/**
* TODO
*
* @author blp
* @date 2020/8/13 21:15
*/
public class MapperProxy implements InvocationHandler {
private Map<String,Mapper> mappers;
private Connection conn;
public MapperProxy(Map<String,Mapper> mappers, Connection conn){
this.mappers=mappers;
this.conn=conn;
}
/**
* 用于对方法进行增强,其实就是调用selectList方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.从map中找到mapper,获取方法名
String methodName=method.getName();
//2.获取方法所在类名称
String className=method.getDeclaringClass().getName();
//3.组合key
String key=className+"."+methodName;
System.out.println("______"+key);
System.out.println(mappers);
//4.获取mappers中的mapper对象
Mapper mapper=mappers.get(key);
//5.判断是否有mapper
if(mapper==null){
throw new IllegalArgumentException("参数有误");
}
//6.调用工具类查询所有
return new Executor().selectList(mapper,conn);
}
}
3.13步骤十三:创建查询工具类
package com.test.mybatis.utils;
import com.test.cfg.Mapper;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* TODO
*
* @author blp
* @date 2020/8/13 21:24
*/
public class Executor {
public <E> List<E> selectList(Mapper mapper, Connection conn){
PreparedStatement pstm=null;
ResultSet rs=null;
List<E> list=new ArrayList<E>();
try {
String queryString=mapper.getQueryString();
String resultType=mapper.getResultType();
Class domainClass=Class.forName(resultType);
pstm=conn.prepareStatement(queryString);
rs=pstm.executeQuery();
while(rs.next()){
E obj=(E)domainClass.newInstance();
ResultSetMetaData rsmd=rs.getMetaData();
int columnCount=rsmd.getColumnCount();
for(int i=1;i<=columnCount;i++){
String columnName=rsmd.getColumnName(i);
Object columnValue=rs.getObject(columnName);
PropertyDescriptor pd=new PropertyDescriptor(columnName,domainClass);
Method writeMethod=pd.getWriteMethod();
writeMethod.invoke(obj,columnValue);
}
list.add(obj);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
release(pstm,rs);
}
return list;
}
public void release(PreparedStatement pstm,ResultSet rs){
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pstm!=null){
try {
pstm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3.14步骤十四:调用测试类进行测试即可
3.15 总结
sqlsessionbuilder接收sqlMapConfig.xml文件流创建sqlsessionfactory工厂-》该工厂读取到xml中的连接数据库和mapper映射信息,用来生产出真正操作数据库的sqlsession对象-》sqlsession有两个作用:1.生成接口的代理对象,2.定义通用增删改查方法-》封装结果集
4.mybatis的crud(基于代理dao的方式)
4.1添加
添加用户信息时配置mapper文件
<!--保存用户信息 id为方法名 parameterType为传入参数的返回值类型 #{username}为实体类中属性值-->
<insert id="saveUser" parameterType="com.itblp.domain.User">
INSERT INTO user (username,sex,address,birthday) VALUES (#{username},#{sex},#{address},#{birthday});
</insert>
编写测试类
class MybatisTest{
public static finally String RESOURCE="SqlMapConfig.xml";
//将创建inputstream,sqlsessionfactory,sqlsession,iuserdao提取出来,因为后后面要释放资源
private InputStream in;
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
private IUserDao userDao;
//创建一个初始化方法,用于初始化inputstream,sqlsessionfactory,sqlsession,iuserdao对像
//@Before注解是在所有测试方法执行之前执行的
@Before
public void init() throws Exception{
in = Resources.getResourceAsStream();
sqlSessionFactory = new SqlSessionFactoryBuilder.build(in);
sqlSession = sqlSessionFactory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
}
//创建一个释放资源的方法用于释放inputstream,sqlsession
//@After注解实在所有测试方法执行之后执行的
@After
public void destroy() throws Exception{
//此时添加一个手动提交,因为mybatis在执行添加方法时会关闭自动提交
sqlSession.commit();
sqlSession.close();
in.close();
}
//查询所有用户
@Test
public void testFindAll(){
List<User> userList = userDao.findAll();
for(User user:userList){
System.out.println(user);
}
}
//添加用户信息
@Test
public void testSaveUser(){
User user = new User();
user.setUserName("zhang");
user.setAddress("xinjiang");
user.setSex("男");
user.setBirthday(new Date());
userDao.saveUser(user);
System.out.println(user.getId());//返回插入成功之后的id,后面有讲
//这里需要有一个手动提交 sqlsession.commit()的方法,提取到公共方法中了,没有的话数据库添加不进去数据
}
}
4.2删除
注意1:删除时如果时包装类型,并且是一个参数,占位符可以随便命名
<delete id=deleteUser parameterType="java.lang.Integer">//parameter的参数可以为INTEGER,INT和java.lang.Integer
DELETE FROM USER WHERE id=#{uid};//其中在实体类中id为id,持久层接口中形参为userid
</delete>
4.3模糊查询
注意2:根据用户名进行模糊查询 有两种方法
方法一:
//dao
List<User> findByName(@Param("uname") String name);
//mapper.xml
<select>
select * from user where username like '%${uname}%'; //或者直接 like '%${value}%' 就不用在dao层使用Param注解了
</select>
方法二:
//dao
List<User> findByName(String name);
//mapper.xml
<select>
select * from user where username like #{
username};
</select>
//但是在测试的时候传参数要带上% %
//例如:方法调用
userDao.findByName("%张%");
区别:
${value}使用的是字符串的拼接。使用的是Statem字符串拼接
#{value}使用的是占位符。使用prepareStatement的参数占位符(主要用这种)
4.4插入之后返回插入后的id
需要使用selectKey标签
<!--保存用户信息 id为方法名 parameterType为传入参数的返回值类型 #{username}为实体类中属性值-->
<insert id="saveUser" parameterType="com.itblp.domain.User">
<!--keyProperty:对应实体类中的属性 keyColumn:对应数据库中的id resultType:返回值类型 order:在什么时候执行,一般在插入之后执行,所以为AFTER-->
<selectKey keyProperty="id" keyColumn="id" resultType="INT" order="AFTER">
select last_insert_id();
</selectKey>
INSERT INTO user (username,sex,address,birthday) VALUES (#{username},#{sex},#{address},#{birthday});
</insert>
5.mybatis中的参数深入和结果集的深入
5.1传递简单类型
5.2传递pojo对象,也就是javabean,实体类对象
mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
ognl表达式:apache开发出来的,全称叫做Object Graphic Navigation Language 对象图导航语言,作用是通过对象中的取值方法来获取数据,在写法上把get给省略了。
比如:我们获取用户的名称,在类中的写法:user.getUserName();
orgn表达式的写法:user.username;
mybatis为什么可以直接写username,而不用user.呢?
因为在parameterType中已经提供了这个属性所属的类,所以不需要写对象名,直接写属性名
5.2将查询条件作为参数
应用在多个对象组成一个查询条件实现数据查询
首先创建一个查询条件的实体类
public class QueryVo{
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
在IUserDao中添加方法
List<User> findByVo(QueryVo queryVo);
mapper配置类
<select id="findByVo" parameterType="com.itblp.domain.QueryVo" resultType="com.itblp.domain.User">
<!--这是传入的参数是QueryVo类型的,所以使用他里面的user属性,user里面又有username属性,所以使用user.username-->
select * from user where username like #{user.username};
</select>
测试类
public void findByVo{
QueryVo queryVo = new QueryVo();
User user = new User();
user.setUsername("%张%");
queryVo.setUser(user);
List<User> userList = userDao.findByVo(queryVo);
for(User user1:userList){
System.out.println(user1);
}
}
5.3结果类型封装
输出pojo对象 此时pojo还是一个实体类
输出pojo列表
解决数据库列名和实体类中属性名不同
1.取别名,执行效率高
2.配置resultMap,需要单另解析一段xml,所以执行效率变低,开发效率会变高
<!--id:自定义名字,查询时用到 type:实体类全限定类名 -->
<resultMap id="userMap" type="com.itblp.domain.User">
<!--主键的配置 property:实体类中的属性名 column:数据库中列名-->
<id property="userId" column="id"></id>
<!--非主键的配置-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
<!--在之后的查询中,就不用resultType了,使用resultMap-->
<select id="findAll" resultMap="userMap">
select * from user;
</select>
6.基于传统dao的方式(了解)
创建实现类,然后在实现类中创建sqlsessionfactory和sqlsession对象,调用sqlsession对象中的方法,例如查询所有的方法
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
public static final String namespace="com.itblp.dao.IUserDao";
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSession=sqlSessionFactory.openSession();
}
public List<User> findAll() {
//2.调用sqlsession中的selectList方法 参数就是可以获取到配置文件中的namespqce中的名称
List<User> userList = sqlSession.selectList(namespace+".findAll");
//3.释放资源
sqlSession.close();
return userList;
}
}
//测试方法 中要将sqlsessionfactory传过去
prepareStatement对象的执行方法:
execute:能执行crud的任何sql语句,返回值是一个boolean类型,表示是否有结果集
executeQuery:只能执行select语句,无法执行增删改,执行结果封装的结果集resultSet对象
executeUpdate:只能执行cud,查询语句无法执行,返回值是影响数据库记录的行数
7.mybatis中的配置(主配置文件:SqlMapConfig.xml)
7.1properties标签
可以使用properties标签内部配置数据库信息:
<!--主配置文件中配置properties属性,可以在标签内部配置数据库的信息,也可以在属性引用外部配置文件信息-->
<properties>
<property name="driver" value="com.jdbc.mysql.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="123"></property>
</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>
使用properties标签引用外部配置文件信息
<!--resource属性是按照类路径的写法,必须存在于类路径下,如果放在resource文件夹根路径下,可以直接写文件名-->
<!-- url属性:按照url的写法来写地址,可以唯一标志一个资源的位置,写法是必须有协议/主机:端口/uri
uri:可以在应用中唯一定位一个资源 例如:file:///C:/Users/think/Desktop/day02_eesy_01mybatiscrud/src/main/resources/jdbcConfig.properties-->
<properties resource="jdbcConfig.properties">
</properties>
<dataSource type="POOLED">
<!--引用外部properties文件需要加上jdbc前缀-->
<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>
外部properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123
7.2typeAliase
<!--在主配置文件中,使用typeAliases设置别名,只能设置domain中类的别名-->
<typeAliases>
<!--type为实体类中的全限定类名,alias为别名,在mapper文件中不区分大小写-->
<typeAlias type="com.itblp.domain.User" alias="user"></typeAlias>
<!--当typeAlias属性过多时,可以注释上面一句,使用package标签,包中的类名就是别名,不区分大小写-->
<package name="com.itblp.domain"></package>
</typeAliases>
7.3mapper
<!--在主配置文件中可以设置mapper标签,将mapper标签换为package标签,package定义持久层接口包名-->
<mappers>
<!--原先写法:使用mapper标签,resource或class属性-->
<mapper resource="com.itblp.dao.IUserDao"></mapper>
<!--当mapper标签过多时,可以注释上面一句,使用package标签的name属性-->
<package name="com.itblp.dao"></package>
</mappers>
8.mybatis中的连接池和事务控制(原理部分了解,应用部分会用)
8.1mybaits中连接池的使用及分析
1.连接池:在实际开发中我们都会使用连接池,因为会减少我们获取连接所消耗的时间
2.mybatis中的连接池:
提供了三种方式的配置:
- 配置的位置:
主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式,取值分别是
-
POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis有针对该规范的实现。从池中获取一个连接用,用完会返回池中
-
UNPOOLED:采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource接口,但是并没有使用池的思想。创建一个新的连接用
-
JNDI: 采用服务器提供的JNDI技术实现来获取dataSource对象,不同的服务器所能拿到的dataSource对象是不一样的
注意: 如果不是web或maven的war工程,是不能使用的。我们实际开发中使用的是tomcat服务器,采用的连接池dbcp连接池
8.2事务控制的分析
mybatis中的事务是通过sqlsession对象的commit方法和rollbaack实现事务的提交和回滚
在sqlSessionFactory.openSession(true)
可以将事务设置成自动提交,也就是在增删改之后不用写sqlsession.commit();
**注意:**在一个方法中提交多条数据,不能设置为自动提交
9.mysql中基于xml配置的动态sql语句使用(会用)
mappers配置文件中的几个标签
9.1if
条件语句
<!--test属性中的值和#{}中的一样,都是类中的属性值,区分大小写,此时没有学习where标签,先用1=1连接-->
<select id="findByCondition" resultMap="userMap">
select id,username,sex,address,birthday from user where 1=1 and
<if test="userName!=null and userName!=''">
username=#{userName}
</if>
<if test="userSex!=null and userSex!=''">
sex=#{userSex}
</if>
<if test="userAddress!=null and userAddress!=''">
address=#{userAddress};
</if>
</select>
9.2where
<!--注意:在test语句中注意and的写法,sql语句以外的区分大小写,以内不区分大小写-->
<!--根据条件查询-->
<select id="findByCondition" resultMap="userMap">
select id,username,sex,address,birthday from user
<where>
<if test="userName!=null and userName!=''">
username=#{userName}
</if>
<if test="userSex!=null and userSex!=''">
sex=#{userSex}
</if>
<if test="userAddress!=null and userAddress!=''">
address=#{userAddress};
</if>
</where>
</select>
9.3foreach
有可能我们查询的id有好几个,例如 select * from user where id in(2,3,5,6);
1.此时我们可以在queryvo中添加一个id的list集合
class QueryVo{
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
2.在接口层写方法
List<User> findUserByIds(QueryVo vo);
3.mapper配置
<sql id="userSql">
select id, username,address,sex from user
</sql>
<!--此时domain层已经在主配置文件中重命名过了,可以直接使用别名 resultMap也已经定义-->
<select id="findUserByIds" parameterType="queryvo" resultMap="userMap">
<include refid="userSql"></include>
<where>
<if test="ids!=null and ids.size()>0">
<!--collection:实体类集合引用 open:sql连接开始 close:sql连接结束 item:每一项定义为uid与#{}中变量一致 sporator:分隔符-->
<foreach collection="ids" open="and id in (" close=")" item="uid" sporator=",">
#{uid}
</foreach>
</if>
</where>
</select>
4.测试(省略)
9.4sql
抽取重复的sql语句
<!--注意:sql语句后面不要加封号,因为到时候有where语句时连接会失败-->
<sql id="userSql">
select id,username,address,sex,birthday from user
</sql>
<!--resulttype表示我们要将结果封装到哪里-->
<select id="findAll" resultMap="userMap">
<include refid="userSql"></include>
</select>
<!--根据条件查询-->
<select id="findByCondition" resultMap="userMap">
<include refid="userSql"></include>
<where>
<if test="userName!=null">
username=#{userName}
</if>
<if test="userSex!=null">
sex=#{userSex}
</if>
<if test="userAddress!=null">
address=#{userAddress};
</if>
</where>
</select>
10.mybatis中的多表操作(掌握)
特例:如果拿出每一个订单,他都只能属于一个用户,所以mybatis把多对一看成了一对一
mybatis中的多表查询:
示例:用户和账户
一个用户可以有多个账户
一个账户只能属于一个用户(多个账户也可以属于一个用户)
步骤:
建立两张表:用户表和账户表 建立外键
两个实体类:用户和账户
3.建立两个配置文件 用户和账户
4.实现配置:
当我们查询用户时,可以同时得到用户下所包含的账户信息
当我们查询账户时,可以同时得到账户所属的用户信息。
10.1一对多
一个用户可以下多个订单,多个订单属于一个用户
一对多关系映射中,主表实体应该包含从表实体的集合引用
private List<Account> accountList;
public List<Account> getAccountList() {
return accountList;
}
public void setAccountList(List<Account> accountList) {
this.accountList = accountList;
}
在配置中,需要在resultMap标签中添加collection标签
<!--定义user的resultmap-->
<resultMap id="userMap" type="user">
<!--主键字段对应 property: column:-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
<!--配置user对象中account集合的映射 ofType:指的是集合中元素的类型-->
<!--property属性是实体类中集合的名称,ofType是集合中每一项的类型,因为此时设置了别名,所以可以直接使用account-->
<collection property="accountList" ofType="account">
<!--注意:此时id需要改名字,否则mybatis分不清是哪个id了-->
<id property="id" column="aid"></id>
<result property="userId" column="user_id"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
10.2多对一
订单和用户
示例:一个账户对应一个用户
1.在账户实体类中添加user实体的引用
public class Account{
private Integer id;
private Integer userId;
private Double money;
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", userId=" + userId +
", money=" + money +
'}';
}
}
2.编写接口层方法
List<Account> findAll();
3.编写mapper配置文件
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"></id>
<result property="userId" column="user_id"></result>
<result property="money" column="money"></result>
<!--建立一对一的映射关系,配置封装user信息-->
<association property="user" column="user_id" javaType="user">
<id property="userId" column="id"></id>
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</association>
</resultMap>
4.测试类
@Test
public void testFindAll(){
List<Account> accountList = accountDao.findAll();
for(Account account:accountList){
System.out.println(account);
System.out.println(account.getUser());
}
}
10.3一对一
一个人只能有一个身份证号,一个身份证号只能属于一个人
10.4多对多
一个学生可以被多个老师教
一个老师可以教多个学生
示例:用户和角色
用户可以有多个角色
角色可以赋予多个用户
步骤:
1.建立两张表:用户表和角色表
需要使用中间表,中间表中需要包含各自的主键
2.两个实体类:用户和角色
各自包含对方一个集合引用
3.建立两个配置文件 用户和角色
4.实现配置:使用的还是collection标签
当我们查询用户时,可以同时得到用户下所包含的角色信息
<!--确定该方法是在那个dao接口-->
<mapper namespace="com.itblp.dao.IUserDao">
<!--定义user的resultmap-->
<resultMap id="userMap" type="user">
<!--主键字段对应 property: column:-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
<collection property="roleList" ofType="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select u.*,r.id as rid,r.role_name,r.role_desc from user u LEFT join user_role ur on ur.uid=u.id LEFT JOIN role r on r.id=ur.rid
</select>
</mapper>
当我们查询角色时,可以同时得到角色的所赋予的用户信息。
<?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.itblp.dao.IRoleDao">
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="userList" ofType="user">
<id property="userId" column="id"></id>
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="roleMap">
select r.id as rid,r.role_name,r.role_desc,u.* from role r LEFT JOIN user_role ru on ru.rid=r.id LEFT JOIN user u on u.id=ru.uid
</select>
</mapper>
11.mybatis中的延迟加载
问题:在一对多中,当我们有一个用户,他有一百个账户。
在查询用户的时候,要不要把关联的账户查出来?
在查询用户时,用户下的账户信息应该是什么时候使用什么时候查询的。
在查询账户的时候,要不要把关联的用户查出来?
查询账户时,账户所属的用户信息应该一起查询出来。
11.1什么是延迟加载
在真正使用数据时,才发起查询,不用的时候不查询,也叫按需加载,懒加载
在一对一中实现延迟加载 即查询账户的时候懒加载用户信息
首先修改account的mapper配置文件
<!--添加一个select属性,值为IUserDao的全限定类名加根据id获取用户信息,此时必须要有column属性,根据id获取用户信息就是通过这个id-->
<association property="user" column="user_id" javaType="user" select="com.itblp.dao.IUserDao.getUserById">
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</association>
<select>
select * from account
</select>
修改主配置文件
<settings>
<!--开启延迟加载的开关-->
<setting name="lazyLoadingEnabled" value="true"></setting>
<!--允许触发方法进行延迟加载-->
<setting name="aggressiveLazyLoading" value="true"></setting>
</settings>
在一对多中实现延迟加载,和一对一其实很相似
mapper配置文件
<resultMap id="userMap" type="user">
<!--主键字段对应 property: column:-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
<!--配置user对象中account集合的映射 ofType:指的是集合中元素的类型 select为account接口中的fandAccountbyId方法-->
<collection property="accountList" ofType="account" column="id" select="com.itblp.dao.IAccountDao.findAccountByUId">
<id property="id" column="id"></id>
<result property="userId" column="user_id"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
11.2什么是立即加载
不管用不用,只要一调用方法,马上发起查询。
在对应的四种关系表中:一对一,多对一,一对一,多对多
一对多,多对多:通常情况下使用延迟加载。
多对一,一对一:通常情况下采用立即加载
12.mybatis中的缓存
12.1什么是缓存
存在内存中的临时数据
12.2为什么使用缓存
减少和数据库的交互次数,提高执行效率
什么样的数据能使用缓存,什么样的数据不能使用
经常查询的数据,并且不经常改变的,数据的正确与否队最终结果影响不大的适用于缓存。
经常改变的数据,数据的正确与否对最终结果影响很大,例如:商品的库存,银行的汇率
12.3mybatis中的一级缓存和二级缓存
一级缓存:指的是mybatis中sqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到sqlSession为我们提供的一块区域中,该区域的结构是一个map。当我们再次查询同样的数据,mybatis会先去sqSession中查看是否有,有的话直接拿出来用,Sqlsession对象消失时,mybatis的一级缓存也就消失了
测试:
//测试mybatis的一级缓存
@Test
public void testFirstCache(){
User user1 = userDao.getUserById(2);
System.out.println(user1);
User user2 = userDao.getUserById(2);
System.out.println(user2);
System.out.pringln(user1 == user2);
//com.itblp.domain.User@1e965684
//com.itblp.domain.User@1e965684
//true
//两个对象是同一个对象,并且后台显示执行这个方法只查询了一次数据库,为了证明sqlsession对象的缓存就是mybatis的一级缓存
//我们将sqlsession对象在第一次使用后关闭,然后再第二次使用前重新创建一个
}
//将sqlsession对象在第一次使用后关闭,然后再第二次使用前重新创建一个
@Test
public void testFirstCache(){
User user1 = userDao.getUserById(2);
System.out.println(user1);
sqlSession.close();
sqlSession = sqlSessionFactory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.getUserById(2);
System.out.println(user2);
System.out.println(user1 == user2);
//com.itblp.domain.User@1e965684
//com.itblp.domain.User@53f65459
//false
//可以看到是不同的对象,并且后台发起了两次查询
}
除了使用sqlsession.close()清除缓存,还可以使用sqlsession.clearCache()来清除缓存
//使用sqlSession中的clearCache来清除缓存
@Test
public void testFirstCache(){
User user1 = userDao.getUserById(2);
System.out.println(user1);
sqlSession.clearCache();
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.getUserById(2);
System.out.println(user2);
System.out.println(user1 == user2);
//com.itblp.domain.User@1e965684
//com.itblp.domain.User@53f65459
//false
//可以看到是不同的对象,并且后台发起了两次查询
}
mybatis如何保持缓存与再次查询数据库中数据的一致性
//mybatis规定在执行了增加,删除,修改,close,commit等操作之后都会将sqlSession销毁,再次使用查询时再重新创建,重新去数据库中查询
//测试一级缓存的同步性
@Test
public void testClearCache(){
User user1 = userDao.getUserById(2);
System.out.println(user1);
user1.setUserName("清华");
user1.setAddress("北京");
userDao.updateUser(user1);
User user2 = userDao.getUserById(2);
System.out.println(user2);
System.out.println(user1 == user2);
//com.itblp.domain.User@1e965684
//com.itblp.domain.User@3b088d51
//false
}
**二级缓存:**它指的是mybatis中的sqlSessionFactory对象的缓存,有同一个sqlsessionfactory对象创建的sqlsession共享其缓存
二级缓存中存放的是数据,而不是对象,如果有新的sqlsession对象要使用这个数据,sqlSessionfactory就从缓存中把数据取出来给这个对象。
二级缓存的使用步骤:
第一步:让mybatis框架支持二级缓存,(在SqlMapConfig.xml中配置)
<settings>
<setting name="cacheEnabled" value="true"></setting>
</settings>
第二部:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
<!--mapper标签中添加-->
<cache/>
第三步:让当前的操作支持二级缓存(在select标签中配置)
<select id="getUserById" parameterType="int" resultMap="userMap" cache="true"></select>
13.mybatis中的注解开发
13.1环境搭建
和使用xml配置的环境 搭建是一样的操作
13.2单表CRUD操作(代理dao方式)
在单表CRUD操作时,使用sql拼接时需要注意
//根据用户姓名模糊查询用户信息,使用sql占位符
@Select("select id,username,address,sex,birthday from user where username like #{username}")
List<User> findUserByName(String name);
//使用sql拼接,使用默认的value传值,方法中的name不用改变
@Select("select id,username,address,sex,birthday from user where username like '%${value}%'")
List<User> findUserByName(String name);
//使用sql拼接,但是不是用value,则方法中的参数要设置Param注解
@Select("select id,username,address,sex,birthday from user where username like '%${username}'")
List<User> findUserByName(@Param("username") String name);
当实体类中属性和mysql中列名不对应时
@Select("select id,username,address,sex,birthday from user")
//Results注解解决数据库列名和类中属性名不一致的情况,id为唯一标识,为的是之后方法也可以使用,value属性是字段对应
@Results(id="userMap",value={
//@Result为字段对应,id=true表示这是唯一主键
@Result(id=true,property="userId",column="id"),
@Result(property="userName",column="username"),
@Result(property="userAddress",column="address"),
@Result(property="userSex",column="sex"),
@Result(property="userBirthday",column="birthday")
})
List<User> findAll();
@Select("select id,address,sex,username,birthday from user where id=#{id}")
//@使用ResultMap注解引用Results注解,可以使字段进行对应
@ResultMap(value={
"userMap"})
User findUserById(Intger id);
13.3多表查询操作
多对一和一对一
1.在Account类中创建user类的引用
public class Account{
//创建一对多关系映射,即一对一关系映射,在从类中创建主类的实体类引用
private User user;
public void setUser(User user){
this.user=user;
}
public User getUser(){
return this.user;
}
}
2.在IAccountDao中添加注解
public interface IAccountDao{
@Select("select * from account")
@Results(id="accountMap",value={
@Result(id=true,property="id",column="id"),
@Result(property="userId",column="user_id"),
@Result(property="money",column="money")
//建立多堆一和一对一的关系映射,@Result注解中有@Result注解,Result注解中有one注解,这个注解就是解决多对一关系的
//select属性是IUserDao中的根据id获取用户信息,column属性决定根据哪一列进行加载,fetchType加载类型选择的是懒加载,需要时才加载
@Result(property="user",column="user_id",one=@One(select="com.itblp.dao.IUserDao.getUserById",
fetchType=FetchType.LAZY))
})
List<Account> findAll();
}
一对多
1.其实和多对一一样,user实体类中添加AccountList集合引用
public class User{
//一对多关系映射
private List<Account> accountList;
public List<Account> getAccountList() {
return accountList;
}
public void setAccountList(List<Account> accountList) {
this.accountList = accountList;
}
}
2.在IUserDao中配置注解
public interface IUserDao{
@Select("select id,username,address,sex,birthday from user")
@Results(id="userMap",value={
@Result(id=true,property="userId",column="id"),
@Result(property="userName",column="username"),
@Result(property="userAddress",column="address"),
@Result(property="userSex",column="sex"),
@Result(property="userBirthday",column="birthday"),
//select为根据id查找account的方法的全限定名,id为根据userid进行查找
@Result(property="accountList",column="id",
many=@Many(select="com.itblp.dao.IAccountDao.findAccountById",fetchType=FetchType.LAZY))
List<User> findAll();
})
}
3.IAccountDao中需要定义根据UserId查找所有的账户信息
public interface IAccountDao{
@Select("select a.id as aid,a.user_id,a.money from account where user_id=#{userId}")
List<Account> findAccountById(Integer id);
}
13.4缓存的配置
测试一级缓存
@Test
public void testFindUserById(){
User user=userDao.findUserById(29);
Sysetm.out.println(user);
User user1=userDao.findUserById(29);
System.out.println(user1);
System.out.println(user==user1);
//一级缓存就是sqlSession对象的缓存,sqlsession对象没有被关闭,所有只在数据库中查询一次,并且是同一个对象
//com.itblp.domain.User_$$_jvst2c8_0@6b9651f3
//com.itblp.domain.User_$$_jvst2c8_0@6b9651f3
//true
}
测试二级缓存
@Test
public void testSecondCache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
User user = userDao.findUserById(29);
System.out.println(user);
//关闭此sqlSession对象
sqlSession.close();
//重新创建sqlSession对象
SqlSession sqlSession1=sqlSessionFactory.openSession();
IUserDao userDao1=sqlSession1.getMapper(IUserDao.class);
User user1 = userDao1.findUserById(29);
System.out.println(user1);
System.out.println(user == user1);
//在没有配置二级缓存之前user和user为不同的对象,并且向数据库中查询两次数据
//接下来进行二级缓存的配置
}
配置二级缓存
主配置文件中开启缓存
<settings>
<setting name="cacheEnabled" value="true"></setting>
</settings>
接口中配置
@CacheNamespace(blocking=true)
public interface IUserDao{
}
配置完成后再测试二级缓存,会发现还是会创建两个对象,但是只会去数据库中查询一次,因为二级缓存共享数据,而不是共享对象。