Mybatis
Javaweb —Mybatis
Mybatis动态代理生成DaoImpl分析;变量使用,动态sql
昨天已经演示了简单使用Maybatis;站在这个角度来说,我们就是用户,开发的mybatis也是一个软件,所以必须按照说明进行操作,不可随意操作 ----->所以需要知道框架怎么写出来得;不然就会被***
昨天看了Mybatis的最主要的一个功能就是可以利用动态代理生成一个Dao接口的实例,不需要自己创建对象了,简单的使用SqlSession的实例方法getMapper就可以创建一个dao对象;所以既然有源码,就看看如何实现的
//类都是一个调用一个,这里可以从最开始的Mapper看起
//首先需要知道SqlSession是一个接口,其默认的实现类是DefaultSqlSession;所以进入实现类中查看方法
return this.configuration.getMapper(type, this);
//发现使用的是configuration的getMapper方法;再进入查找这个方法
return this.mapperRegistry.getMapper(type, sqlSession);
//还是只是简单的使用mapperRegistry ---- mapper注册的getmapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); ----->使用的是HashMap的方式
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
从这里就差不多可以看到动态代理的踪迹,首先进行强制转型
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
之后调用newInstance方法创建了一个实现类
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
//上面的方法先创建了一个InvacationHandler对象,也就是MapperProxy;
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
//这里和之前分析动态代理的创建代理类的方法相同,都是使用的专业的代理类Proxy,使用其类方法newProxyInstance;而这里的代理的委托类就是this.mapperInerface;使用的调用处理器就是MapperProxy;
因为调用处理器中的代码才是处理业务的核心代码,这里可以看看
public class MapperProxy<T> implements InvocationHandler, ---- 实现了InvacationHandler接口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try { ---->proxy是代理类的内对象,后面的方法和返回值就是需要进行代理的对象
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
这里使用了三目符,如果这个方法和对象的类是同一个类才执行方法;所以通过方法也可以获取类;所以核心代码不再这里,这个代理类只是完成了原来的方法,没有进行功能增强;只是对于不匹配进行了处理
之前些的时候已经使用过了,通过日志也可以发现就是使用的JDBC的预编译类型;也就是如何在Mybatis中实现传参呢? JDBC是直接?然后set即可;在Mybatis中,这个?要怎么表示
从java代码中将数据传入到Mapper文件的sql语句中 — 传参
parameterType : mapper文件中select等标签的一个属性,表示dao接口中方法传入的的数据类型;parameterType的值是java的数据类型的全限定名称,或者是Mybatis定义的别名; 比如说java.lang.String ----- 这个参数不是强制的,因为Mybatis可以通过反射来获取参数的数据类型
Mybatis将java中的基本数据类型和String叫做简单类型;传参很简单;也就是说一个参数因为只有一个参数,所以随意命名都可以,都会定义到传入的那个参数
#{任意的字符}
SELECT * FROM student WHERE stuno = #{id}
使用#,mybatis使用的就是PreparedStatement对象;然后就是conn.preparedStatement(sql) ----- 增强的JDBC
当Dao的接口方法多个参数时,需要通过名称获取参数,在方法形参前面加上自定义@param{"自定义参数名“},mapper文件就可以使用#{自定义参数名}
所以这种方式想要自定义,必须加上注解@Param
接口的方法
public List<Student> selectStudents(@Param("name") String stuname,@Param("class") String stuclass);
mapeper文件
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{name} AND stuclass = #{class};
</select>
这样子测试的时候可以发现
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ? AND stuclass = ?;
==> Parameters: 张三(String), HC2001(String)
<== Columns: stuno, stuname, stuclass
<== Row: 1, 张三, HC2001
<== Total: 1
就可以成功的进行传参;自动识别了parameterType为String类型
Mybatis通过Dao接口中的方法进行反射获取的实现类,所以在Dao接口中尽量不使用重载方法
使用java对象传递参数,java的属性值就是sql需要的值,每一个属性就是一个参数,语法格式就是#{property.javaType.数据类型名称,jdbcType=数据类型名称}; javaType、jadbType的类型Mybatis可以通过反射获取,不需要色湖之,常用的格式就是#{prperty} ----- 这里的属性名和类中的属性名一定要一致,否则检测不出来;昨天的查询出的结果自动赋值给ResultType的Student类型,这里也需要对应,名称一致,不然无法成功赋值
public int insertStudent(Student student);
<!--插入学生 -->
<insert id="insertStudent">
INSERT INTO student (stuno,stuname,stuclass) VALUES (#{stuno},#{stuname},#{stuclass});
</insert>
//这里其中的名称一定就是属性的名称
如果是最规范的形式;这里jdbcType可以查文档获取支持的类型的名称
VALUES (#{stuno,javaType=int,jdbcType=INT},#{stuname,javaType=java.lang.String,jdbcType=VARCHAR},#{stuclass,javaType=java.lang.String,jdbcType=VARCHAR});
不一定所有的属性都要使用,传入对象,只使用对象的某一个属性或者某几个属性也是可以的
public List<Student> selectStudents(Student student);
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{stuname}; --->这里一定要是属性名
</select>
这种方式也就可以成功的传参
Student student = new Student(14,"新亚","HC2003");
List list = dao.selectStudents(student);
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ?;
==> Parameters: 新亚(String)
<== Columns: stuno, stuname, stuclass
<== Row: 14, 新亚, HC2003
<== Total: 1
上面的传参的类型可以理解为JDBC中的setString(参数名称);
当时使用的时候还有一个方法就是setString(int columIndex);对应在Mybatis中也有一个按位置传参
参数的位置从0开始【JDBC从1开始】,引用参数的语法是#{arg位置},第一个参数是#{arg0},第二个是arg1;新版本的mybatis一定要记得加上arg
这种方式就和之前的根据位置set相同;但是不推荐,因为参数的位置可能发生变化
public List<Student> selectStudents(int stuid,String name);
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{arg1} OR stuno = #{arg0};
</select>
List<Student> list = dao.selectStudents(14,"张三");
执行的结果
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ? OR stuno = ?;
==> Parameters: 张三(String), 14(Integer)
<== Columns: stuno, stuname, stuclass
<== Row: 1, 张三, HC2001
<== Row: 14, 新亚, HC2003
<== Total: 2
这里一定要注意顺序,所以不建议使用
Map集合可以存储多个值,使用Map向mapper文件一次性传入多个对象,Map集合使用String类型的key,Object类型存储参数,mapper问价使用#{key}引用参数值;Map的功能十分强大,所以要多多关注Map的使用
Map<String,Object> map = new HashMap<String,Object>():
map.put("name","张三");
map.put("stuclass","HC2001");
接口方法
List<Student> selectStudent(Map<String,Object> map);
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{name} OR stuno = #{stuclass};
</select>
但是这里的局限就是map中的key一旦改动,那么就都要改动,并且传入的是map,表面上是看不出来有参数的个数和参数的类型的,所以也不推荐;推荐使用@Param和对象类型传参
#:占位符
: 告诉mybatis用实际的参数值代替,和JDBC的?类似;并使用PreparedStatement对象执行sql语句,#{……}代替了sql语句的"?",这样更加安全 ---- 因为之前?可以任意匹配,但是这里限制了
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{arg1} OR stuno = #{arg0};
select>
在mybatis中执行就是先转化为 ?; 然后使用PreparedStatement对象进行预编译,之后再进行传参;也就是使用set方法
$ 字符串替换
: 告诉mybatis使用$包含的字符串 来替换所在位置; 使用Statement把sql语句和 的内容连接起来,主要使用再替换表名、列名,不同列排序的时候操作 ----- 其实就是使用的Statement进行拼接之后才编译;再JDBC中提过会出现sql注入的现象
也就是说${……}对应的是JDBC的拼接的方式,对应的是Statement,jdbc中直接拼接,拼接之后再编译
#{……}对应的是JDBC的预编译的方式,对应的是Statement,先编译,后进行传值
这里可以看一下两种方式的区别
public List<Student> selectStudents(Student student);
//简单使用对象进行传值
Student student = new Student(14,"新亚","HC2003");
List<Student> list = dao.selectStudents(student);
1.使用#占位符的方式
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{stuname};
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ?;
==> Parameters: 新亚(String)
<== Columns: stuno, stuname, stuclass
<== Row: 14, 新亚, HC2003
<== Total: 1
-------------------------------------------------------------------------
2.使用$字符串替换的方式
SELECT stuno,stuname,stuclass FROM student WHERE stuname = ${stuname};
//换了之后就不会先预编译了,而是直接将$中的内容拼接到sql语句中
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column '新亚' in 'where clause'
### The error may exist in cfeng\dao\StudentDao.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT stuno,stuname,stuclass FROM student WHERE stuname = 新亚;
### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新亚' in 'where clause'
可以看到,报错就是因为通过域属性的方式取出的就是属性值,但是这样直接拼接没有引号,就报错了
那么因为这里是直接将{}得到的值直接拼接到sql语句,所以加上单引号
SELECT stuno,stuname,stuclass FROM student WHERE stuname = '${stuname}';
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = '张三';
==> Parameters:
<== Columns: stuno, stuname, stuclass
<== Row: 1, 张三, HC2001
<== Total: 1
可以看到使用$方式就是直接进行了sql的拼接再执行,没有预编译的过程,使用的就是Statement对象;这样就会发生sql注入了,比如这里的stuname改一下
Student student = new Student(14,"张三' OR stuclass = 'HC2002","HC2003");
//然后再执行
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = '张三' OR stuclass = 'HC2002';
==> Parameters:
<== Columns: stuno, stuname, stuclass
<== Row: 1, 张三, HC2001
<== Row: 2, 李四, HC2002
<== Row: 3, Cfeng, HC2002
<== Row: 13, 奥利, HC2002
<== Total: 4
发生了sql注入,所以还是要使用占位符#的方式或者JDBC中的PreparedStatement;效率要比PreparedStatement低;能确定数据安全,就使用$来替换列名和表明
这个问题之前JDBC位置没有明确说明,这里说明一下;字段名、表名不能使用占位符?
;从上面的操作就可以看出来,当使用#的时候,直接写即可;但是换成$,就必须加上单引号; 这说明PreparedStatement会自动为占位符的内容加上引号, 也就是说PreparedStatement只能允许传值
但是像表名、字段名、还有升序降序等内容在sql语句中是直接书写的,不能加上引号,所以这个时候就只能使用字符串的拼接,在JDBC中就是Statement;在Mybatis中就是$的方式
Preparement就是预编译,字段名和表明是sql语句的核心,所以在Preparedment中已经固定了,不能修改,只是允许动态改变字段的值;写sql语句中,字段名的值才是可以传递的,使用?进行占位;这个值不管字段是什么类型,都是需要加上引号的
$可以用来替换表明,列名,还有进行排序的时候使用,比如
public List<Student> selectStudents(String colname);
SELECT stuno,stuname,stuclass FROM student ORDER BY ${coname};
List<Student> list = dao.selectStudents("stuname");
//操作的结果
==> Preparing: SELECT stuno,stuname,stuclass FROM student ORDER BY stuname;
==> Parameters:
<== Columns: stuno, stuname, stuclass
按照stuname进行升序排列
而使用#是达不到效果的---- 字段名不是一个值,不需要加引号,因为是预编译;preparedStatement适合执行多次,Statement适合执行一次,一般不会给用户注入的机会
总的来说
在JDBC中查询sql语句输出的结果会有ResultType进行接收,那么在Mybatis中是借助的标签进行处理,这里赋值给对象的时候要要求对应;查询语句都需要使用result啊Type或者resultMap进行处理
resultType: 执行sql得到的Result转换的java对象类----------使用类型的全限定名或者别名;
注意如果返回的是集合,那么设置为集合中对象的数据类型;而不是集合本身 ------ 因为使用的result进行赋值,而集合中没有相关的,resultType和resultMap不能同时使用 ------ 【只要类中有对应查询结果的属性即可 — 不然都是默认值】 — 不一定非得是实体类【只要有属性来存储这个结果即可—类可以没有意义】
处理的方式:
感兴趣的可以看看源码的这个功能的实现;
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student ORDER BY ${coname};
</select>
这里执行的时候就会创建一个Student对象,然后将resultSet的结果赋值给这个对象;使用jdbc就类似于这样
while(result.next()) {
Student student = new Student();
student.setStuno(result.getInt("stuno"));
Studnet.setStuname(result.getString("stuname"));
Student.setStuclass(result.getString("stuclass"));
}
多表联查也是相同得,只要是同名的名cheng赋值给同名得属性即可
简单类型如何处理?
上面演示的是返回的是对象类型,那么如果返回的是简单类型怎么办呢? 比如 SELECT COUNT(*) FROM student; 这里返回的就是一个整型的值;这种情况,resultType就用mybatis定义的简单类型的别名即可
public int selectStudents();
<select id="selectStudents" resultType="_int">
SELECT COUNT(*) FROM student;
</select>
System.out.println("表中数据的行数 = " + dao.selectStudents());
可以看到,这里返回的是int类型,所以直接将resultType设置为mybatis支持的别名就是_int既可以了
==> Preparing: SELECT COUNT(*) FROM student;
==> Parameters:
<== Columns: COUNT(*)
<== Row: 12
<== Total: 1
表中数据的行数 = 12
注意,这里的_int就是int的别名;int是Integer的别名
There are many built-in type aliases for common Java types. They are all case insensitive, note the
special handling of primitives due to the overloaded names.
Alias Mapped Type
_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没有提供,这个时候就需要进行自定义
自定义类型的别名 typeAlias
在mybatis的主配置文件中,使用< typeAlias>定义别名
位置是在configuration标签下面
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGGING"/>
settings>
<typeAlias>
<typeAlias type="cfeng.entity.Student" alias="student"/>
typeAlias>
configuration>
定义之后就可以在mapper文件的select标签中使用这个别名了
也就是先定义就可以使用这个别名,一行就可以定义一个别名
还有一种方式就是给直接使用package标签,定义包,包中的所有的都可以起别名;别名就是类名
<typeAlias>
<package type="cfeng.entity"/>
typeAlias>
这样包中的所有的都可以直接使用类名充当别名;一行可以指定一个包;
但是不建议用别名,因为别名会出现冲突
前面的方式都是将查询结果创建为一个对象,然后进行返回,其实还可以使用map的方式进行接收,查询结果直接放在Map中; 是将查询的字段和值直接放到map中,所以要求只能有一条结果
public Map<String,Object> selectStudents();
<select id="selectStudents" resultType="java.util.HashMap">
SELECT stuno,stuname,stuclass FROM student WHERE stuno = '1';
</select>
Map<String,Object> resMap = dao.selectStudents();
System.out.println("map = " + resMap );
resMap.keySet().forEach(key->System.out.println( key+ " : " + resMap.get(key)));
这里可以看一下输出的结果
map = {stuclass=HC2001, stuname=张三, stuno=1}
stuclass : HC2001
stuname : 张三
stuno : 1
可以看到查询结果就是以字段名 ->值 方式放在Map中,所以这里只能放一个对象的属性,也就是查询一条记录;多条记录或报错;key是列名,value是列值
Expected one result (or null) to be returned by selectOne(), but found: 12
以map的形式就只能返回一条或者0条记录,不能返回多条
这是结果映射,指定列名和java对象的属性之间的关系: 1.自定义列值赋值给某个属性,当不一样的时候就只能使用resultMap
resultMap可以自定义sql的结果和java对象的映射关系,更灵活把列值赋值给指定属性,常用在列名和java对象属性名不一样的时候
使用的方式:
<resultMap id="studentMap" type="cfeng.entity.Student">
<id column="stuno" property="stuid"/>
<result column ="stuname property="stuname/>
<result column="stuclass" property="stuclass"/>
resultMap>
<select id="selectStudents" resulMap="studentMa">
SELECT stuno,stuname,stuclass FROM student;
</seMap
当属性名和字段名不一致的时候,还可以使用sql的起别名的方式
还有另外一总方式可以解决这个问题,restultType的原则是同名的列值赋值给同名的属性;之前JDBC中就提到过最终结果的name是查询出来的名称,所以起别名就可以解决问题
SELECT stuno AS stuid,stuname,stuclass FROM stuname;
在Mysql中,一般就直接使用的LIKE进行的模糊查询,当时使用%进行占位,比如%m%,就是查询含有m的,在mybatis中,要进行模糊查询,一共有两种方式
也就是说因为#会将{}中的内容作为参数赋值给预编译的sql语句,所以可以直接指定内容为通配
public List<Student> selectStudents(String name);
// 这里只一个简单类型参数,所以可以自定义名称
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname LIKE #{name};
</select>
//在java代码中指定Like的内容
String name = "%里%";
List<Student> list = dao.selectStudents(name);
这样就可以查询出所有带有”里“的学生
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname LIKE ?;
==> Parameters: %里%(String)
Student{stuno=7, stuname='里斯', stuclass='HC2003'}
Student{stuno=10, stuname='里仁', stuclass='HC2004'}
因为Like的内容也是字段的值,所以使用的方式没有任何的区别
这种方式就是要修改配置文件,传递的时候传递模糊的内容即可
SELECT stuno,stuname,stuclass FROM student WHERE stuname LIKE '%' #{name} '%';
String name = "里";
List<Student> list = dao.selectStudents(name);
这种方式需要进行修改不推荐,反正也是值,所以当作普通的处理就可以了
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname LIKE '%' ? '%';
动态SQL: 也就是sql的内容是变化的,可以根据条件获取到不同的sql语句,主要是where部分发生变化,动态sql的实现依靠的是mybatis提供的标签,比如< if>, < where>, < froeach>
使用动态SQL的时候最好使用对象进行传参,因为判断条件就是根据的传入对象的属性来进行判断;这也是最常用的方式
; 因为动态sql不定使用几个参数,对象传参可以完美的使用任意个参数
用来判断条件的,使用的方式就是根据其属性test;if内部就是部分的sql语句
<if test="判断java对象的属性值">
部分sql语句
if>
对于这个标签执行,当test的值为true时,会将其包含的sql片段拼接到其所在的SQL语句中
其实就类似于java中的if条件判断,标签包含部分sql语句,所以这里就将这个标签放在select等标签的内部,放在语句的前后, 这里执行的条件就是使用java对象的属性值作为判断条件, 属性值 ……;所以这里使用Student的stuname属性
//这里还是简单使用查询的操作
public List<Student> selectStudents(String name);
//select标签中再写
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname = #{name}
<if test="stuname == '张三'">
OR stuno > 5;
</if>
</select>
这样测试代码中传入不同的stuname,就会执行不同的代码
//传入参数为 里斯
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ?
==> Parameters: 里斯(String)
----------------------------------------------------
//传入参数为张三
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ? OR stuno > 5;
==> Parameters: 张三(String)
所以除了可以得出传入的值不同执行的sql语句不同之外还可以得出,mybatis中的sql语句不需要加分号,自动会结尾 ----- 这里不能再执行其他的sql语句
那么有多个条件怎么办?在java中使用else if
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student ---- 主sql语句
WHERE
<if test="stuname == '张三'">
stuno > 5
if>
<if test="stuname=='里斯'">
stuno < 5
if>
select>
这里要考虑多种情况,综合考虑是否会同时成立,同时成立如何处理;一般情况下会在主SQL位置加上一个恒成立的不影响的条件比如 ‘1’ = ‘1’
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student
WHERE '1' = '1'
<if test="'">
AND stuno = #{stuno}
if>
<if test="">
AND stuname = #{stuname}
if>
select>
这样子就不会存在执行条件不同满足情况导致的SQL语句出现错误;但是这样子会让SQL语句不是想要的类型;有没有好的解决办法呢?
这个标签用来包含多个< if>标签的,当多个if中有一个成立的时候,< where>会自动增加一个where关键字,并去掉if中多余的 AND、 OR等;所以不需要再加 1 = 1 还有就是
也就是说,一旦wehere标签中某个条件成立,会自动加上where;并且会自动取出多余的AND和OR
public List<Student> selectStudents(Student student);
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student
<where>
<if test="stuno < 3">
stuname = #{stuname}
</if>
<if test="stuclass == 'HC2001'">
OR stuno > #{stuno}
</if>
</where>
</select>
这里之前的写法是直接在test中写的> 或者< ;结果会报错,这里就要使用转义字符
与元素类型 "if" 相关联的 "test" 属性值不能包含 '<' 字符。
常用的几个转义字符就是在html中使用的那几个;> ; < ;
Student student = new Student(1,"张三","HC2001");
List list = dao.selectStudents(student);
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ? OR stuno > ?
==> Parameters: 张三(String), 1(Integer)
这里可以换一个条件
Student student = new Student(1,"张三","HC2003");
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname = ?
这个标签用于实现对于数组和集合的遍历、对其使用,主要用在sql语句的in语句中
, 比如 SELECT stuno FROM student WHERE stuname IN (“lisi”,“ssih”) 需要注意
<foreach collection="集合类型" open="开始的字符" close="结束的字符" item="集合的成员" separator="集合成员之间的分隔符">
#{item的值}
foreach>
这里就按照上面的语法来就可以,这里的collection的类型就是list;因为时IN,所以开始的符号时(;结束的符号时); 分隔符就是, ; 这里的item就是代指的传入集合的项;也就是for循环中的i ; 下面调用这个i也就是通过 #{i}的方式;
public List<Student> selectStudents(List<String> nameList);
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuname IN
<foreach collection="list" item="thename" open="(" close=")" separator=",">
#{thename}
</foreach>
</select>
List<String> nameList = new ArrayList<>();
nameList.add("张三");
nameList.add("cfeng");
nameList.add("奥利");
List<Student> list = dao.selectStudents(nameList);
这样就可以将List集合中的简单的对象放到域中;然后使用IN来执行SQL语句
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuname IN ( ? , ? , ? )
==> Parameters: 张三(String), cfeng(String), 奥利(String)
可以看到mybatis自动将foreach中的转变为了完整的一个IN的sql语句
List集合既可以放简单的类型,也可以放复杂的类型,上面的简单的类型直接将集合中的数据给取出放到{}中即可,对于对象类型,item就是一个对象,那么使用什么属性,就直接.引用即可
public List<Student> selectStudents(List<Student> stuList);
<select id="selectStudents" resultType="cfeng.entity.Student">
SELECT stuno,stuname,stuclass FROM student WHERE stuno IN
<foreach collection="list" item="stu" open="(" close=")" separator=",">
#{stu.stuno}
</foreach>
</select>
List<Student> stuList = new ArrayList<>();
stuList.add(new Student(1,"张三","HC2001"));
stuList.add(new Student(3,"cfeng","HC2002"));
stuList.add(new Student(13,"奥利","HC2002"));
List<Student> list = dao.selectStudents(stuList);
这里所以使用简单类型,就相当于for循环中的item都是一个对象,要使用什么就使用什么,直接.引用即可
==> Preparing: SELECT stuno,stuname,stuclass FROM student WHERE stuno IN ( ? , ? , ? )
==> Parameters: 1(Integer), 3(Integer), 13(Integer)
对于数组也是类似的用法,对象类型就直接引用即可,foreach标签经常使用在IN语句中
< sql>这个标签可以用于定义SQL片段,以便实现其他SQL标签的复用,而其他的标签使用这个SQL片段,需要使用< include>子标签,这个标签可以定义SQL语句中的任何部分,所以这标签可以使用在动态SQL的任何位置
<sql id="自定义的名称">
SELECT stuno,stuname,stuclass FROM student
sql>
这里是因为很短,但是有的时候比如多表联查的时候sql语句会很长,所以这个时候就要使用这个标签进行简化
在想使用的地方,直接使用include即可
<select>
<include refid="sql的名称"/>
select>
先使用sql进行定义,就可以使用include进行使用
这里可以将之前的那个例子的主sql语句给提取出来进行复用
<mapper namespace="cfeng.dao.StudentDao">
<sql id="studentSql">
SELECT stuno,stuname,stuclass FROM student
sql>
<select id="selectStudents" resultType="cfeng.entity.Student">
<include refid="studentSql">include>
WHERE stuno IN
<foreach collection="list" item="stu" open="(" close=")" separator=",">
#{stu.stuno}
foreach>
select>
mapper>
这里就会将上面的sql代码自动放到include的位置和后面的WHERE IN 拼在一起
主配置文件的标签之前分析过,这里详细来看一下;
settings 设置mybatis的行为的,其中有很多标签,比如UseGenericKey等,这里后面如果使用到了会进行分析的,但开始一般情况下不用设置;可以直接在mybatis的文档中就可以去找
environments 用来配置连接数据库的环境的,在一个数据库的环境environment中
Connection getConnection() throws SQLException;
进入方法发现这个类中有方法就是获取数据库连接的,所以dataSource就可以用来表示数据库的连接
将数据库的连接信息放到一个单独的文件,和mybatis的主配置文件分开,目的是便于修改、保存,处理多个数据库的信息
在resource目录中定义一个属性配置文件
,xxx.properties,在属性配置文件中放置数据库的相关信息,和之前使用反射模式相同;一般使用多级目录//这个直接放在resource目录的下面
##############mysql database configuration##########################
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/cfengbase?servertimezone=GMT%2B8
jdbc.user = cfeng
jdbc.password = ********
这里直接在configuration标签下写properties标签即可;然后在之前的environment中的property标签中使用
jdbc.driver<configuration>
<properties resource="db.properties"/>
<environments default="myenv">
<environment id="myenv">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
configuration>
在mapper标签中可以指定mapper文件的位置,这里一个mapper文件对应的是一个Dao接口;所以对于多张表就会存在多个dao接口进行操作; 其实这里的操作和起别名类似; 一个一个使用typeAlias非常麻烦,可以直接使用包来搞定,这里同理【起别名是在typeAlias标签,这里是mappers标签】
<mappers>
<mapper resource="cfeng\dao\StudentDao.xml"/>
<mapper resource="cfeng\dao\TeacherDao.xml"/>
……
mappers>
但是这种方式的缺点就是当表非常多的时候就会出现很多的mapper文件,不好指定
<mappers>
<package name="cfeng.dao"/>
mappers>
但是这里的要求就是,
这个是专门用来进行数据分页的,比如对于搜索百度的,上面的内容就是一页页显示,在Mysql中就是LIMIT实现的,把表中的数据分批次进行取出;
SELECT * FROM student LIMIT X,3; ---- pageHelper就可以支持各种不同的数据库进行分页;
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.3.0version>
dependency>
<settings>
……
settings>
<typeAlias>
……
typeAlias>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
plugins>
<environments default="myenv">
……………………
public List<Student> selectStudents();
<select id="selectStudents" resultType="cfeng.entity.Student">
<include refid="studentSql"></include>
</select>
StudentDao dao = MybatisUtil.getSqiSession().getMapper(StudentDao.class);
//加入pageHelper的方法进行分页
//pageHelper的静态方法starpage 这里的pageNum表示第几页,从第一页开始,pageSize是一页放几行数据
PageHelper.startPage(1,3);
List<Student> list = dao.selectStudents();
list.forEach(System.out::println);
这里可以看一下效果,其实就是查询的时候会自动加上LIMIT
==> Preparing: SELECT count(0) FROM student
==> Parameters:
<== Columns: count(0)
<== Row: 12
<== Total: 1 ------>首先会查询数据表中有多少条记录,/size得到可以分页的数量
==> Preparing: SELECT stuno,stuname,stuclass FROM student LIMIT ? ---->判断取第一页的3条,所以就是前三条,自动使用Limit
==> Parameters: 3(Integer)
<== Columns: stuno, stuname, stuclass
<== Row: 1, 张三, HC2001
<== Row: 2, 李四, HC2002
<== Row: 3, Cfeng, HC2002
<== Total: 3
Student{stuno=1, stuname='张三', stuclass='HC2001'}
Student{stuno=2, stuname='李四', stuclass='HC2002'}
Student{stuno=3, stuname='Cfeng', stuclass='HC2002'}
PageHelper这个插件不是mybatis框架中的,但是mybatis框架可以使用,这个可以方便的帮助进行各种数据库的分页,当然也可以手工计算,反正都是使用LIMIT 以后会补充一些mybatis的内容