一:首先是动态sql
MyBatis支持动态SQL,即根据不同的情况生成不同的SQL语句。这种机制可以帮助开发者编写更加灵活的SQL语句,从而满足不同的需求。MyBatis的动态SQL主要有以下几种方式:(基于xml方式可以实现,注解的方式本人不推荐)
1.<if>
2.<where>
3.<set>
4.<sql><include>
接下来逐个详细的解释
- If语句:可以使用if语句根据不同的条件控制SQL语句的执行。如果条件成立,则拼接相应的SQL语句;否则忽略该语句段。
举例来说,假设需要查询某个表中年龄在18到30之间的人员信息,则可以使用如下SQL语句
SELECT * FROM user WHERE 1=1
<if test="age!=null">
AND age BETWEEN #{minAge} AND #{maxAge}
</if>
如果用户指定了最小年龄和最大年龄,则会生成下面的SQL语句:
SELECT * FROM user WHERE 1=1 AND age BETWEEN ? AND ?
如果用户未指定年龄,则只会生成下面的SQL语句:
SELECT * FROM user WHERE 1=1
2.Where语句:可以使用where语句根据不同的条件拼接不同的SQL语句。如果第一个条件为true,则添加where子句;否则添加and子句。
例如,需要查询某个表中符合条件的人员信息,但是可能存在多种查询条件,则可以使用如下SQL语句:
SELECT * FROM user
<where>
<if test="name!=null">
AND name=#{name}
</if>
<if test="age!=null">
AND age=#{age}
</if>
</where>
如果用户指定了姓名和年龄,则会生成下面的SQL语句
SELECT * FROM user WHERE name=? AND age=?
如果用户只指定了姓名,则会生成下面的SQL语句:
SELECT * FROM user WHERE name=?
如果用户只指定了年龄,则会生成下面的SQL语句:
SELECT * FROM user WHERE age=?
set标签用于动态生成UPDATE语句中的SET子句。下面是对set标签的详细解释:
<sql>
和<include>
标签都是MyBatis中动态SQL的一部分。
<sql>
标签用于定义可重用的SQL片段,可以在任何地方引用这些片段。使用<sql>
标签定义SQL片段后,可以在其他语句中使用<include>
标签来插入该片段。
<include>
标签用于将一个定义好的SQL片段包含在另一个SQL语句中,以实现SQL语句的复用。使用<include>
标签时,需要指定引用的SQL片段的id属性值,例如:
<sql id="userColumns">
username, password, email
</sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"/>
from users
</select>
上示例中,<sql>
标签定义了一个名为userColumns
的SQL片段,包含了用户表中的三个列。在<select>
语句中,使用<include>
标签将这个SQL片段包含在了查询语句中。在执行查询时,MyBatis会将<include>
标签替换成对应的SQL片段,从而生成完整的SQL语句。
使用<include>
标签可以有效地减少SQL语句的冗余,提高代码的可维护性和复用性。
二实现分页查询的两种方式:
MyBatis 实现分页查询通常有两种方式:基于数据库的分页和基于应用程序的分页。
- 基于数据库的分页
基于数据库的分页是通过 SQL 语句中添加 LIMIT 或者 OFFSET 子句来实现的。例如:
<select id="selectUsers" resultType="User">
SELECT * FROM users LIMIT #{offset}, #{limit}
</select>
其中,#{offset}
表示偏移量(即从第几条记录开始),#{limit}
表示每页显示的记录数。在进行分页查询时,应用程序首先计算出 offset 和 limit 的值,然后将它们传递给 MyBatis 执行相应的 SQL 语句。
优点:性能较好,因为只返回了需要显示的数据。
缺点:SQL 语句比较复杂,不利于调试和维护;如果使用了多表连接或者复杂的查询条件,可能会导致性能下降。
2.基于应用程序的分页
基于应用程序的分页是在查询结果集合中进行分页的。例如,在查询所有用户信息之后,应用程序通过 Java 代码进行分页处理:
int startIndex = (pageNum - 1) * pageSize;
List<User> users = userDao.selectAll();
List<User> pageUsers = new ArrayList<>();
for (int i = startIndex; i < startIndex + pageSize && i < users.size(); i++) {
pageUsers.add(users.get(i));
}
其中,pageNum
表示当前页码,pageSize
表示每页显示的记录数。首先,应用程序调用 selectAll()
方法查询所有用户信息,然后根据 pageNum 和 pageSize 计算出需要显示的数据范围,并将符合条件的用户信息存入 pageUsers 集合中。
优点:代码简单易懂,易于调试和维护;适用于所有情况,不会因为复杂的 SQL 语句而影响性能。
缺点:在大量数据的情况下可能会占用较多系统内存。
综上所述,基于数据库的分页和基于应用程序的分页各有优缺点,具体使用哪种方式应根据实际情况进行选择。
3.基于插件的分页查询
MyBatis 还提供了一种基于插件的分页查询方式。通过自定义 MyBatis 插件,在查询数据时截获 SQL 语句并进行修改,从而实现分页查询。
具体实现方式如下:
- 自定义一个 MyBatis 插件,并实现
Interceptor
接口。在intercept()
方法中,可以获取到当前执行的 SQL 语句,然后根据传入的分页参数对 SQL 语句进行修改。例如:
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (!mappedStatement.getId().matches(".+ByPage$")) {
return invocation.proceed();
}
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
Page page = null;
Object parameterObject = boundSql.getParameterObject();
if (parameterObject instanceof Page) {
page = (Page) parameterObject;
} else {
Field[] fields = parameterObject.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getType() == Page.class) {
field.setAccessible(true);
page = (Page) field.get(parameterObject);
break;
}
}
}
if (page == null) {
return invocation.proceed();
}
String countSql = "SELECT COUNT(*) FROM (" + sql + ") tmp_count";
Connection connection = (Connection) invocation.getArgs()[0];
PreparedStatement countStatement = connection.prepareStatement(countSql);
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
setParameters(countStatement, mappedStatement, countBoundSql, parameterObject);
ResultSet rs = countStatement.executeQuery();
int totalCount = 0;
if (rs.next()) {
totalCount = rs.getInt(1);
}
rs.close();
countStatement.close();
page.setTotalCount(totalCount);
String pageSql = generatePageSql(sql, page);
metaObject.setValue("delegate.boundSql.sql", pageSql);
return invocation.proceed();
}
private void setParameters(PreparedStatement preparedStatement, MappedStatement mappedStatement, BoundSql boundSql, Object parameter) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
Configuration configuration = mappedStatement.getConfiguration();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
MetaObject metaObject = parameter == null ? null : configuration.newMetaObject(parameter);
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
PropertyTokenizer prop = new PropertyTokenizer(propertyName);
if (parameter == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameter.getClass())) {
value = parameter;
} else if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
value = boundSql.getAdditionalParameter(prop.getName());
if (value != null) {
value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
}
} else {
value = metaObject == null ? null : metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
if (typeHandler == null) {
throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
}
typeHandler.setParameter(preparedStatement, i + 1, value, parameterMapping.getJdbcType());
}
}
}
}
private String generatePageSql(String sql, Page page) {
StringBuffer pageSql = new StringBuffer();
pageSql.append(sql);
pageSql.append(" limit ").