面试题答案
一键面试SQL语句硬编码反模式
描述
在DAO层代码中,将SQL语句直接写死在Java代码中,而不是通过参数化查询或从外部配置文件加载SQL。例如:
public User getUserById(int id) {
String sql = "SELECT * FROM users WHERE id = " + id;
// 执行SQL代码
}
可能带来的问题
- 维护困难:如果数据库表结构或查询逻辑发生变化,需要修改Java代码中的SQL语句,这可能会影响到多个地方,增加维护成本。例如,若
users
表名变更,所有硬编码该表名的SQL都需修改。 - SQL注入风险:如上述代码,当
id
值由用户输入提供时,恶意用户可能输入特殊字符构造恶意SQL语句,导致数据泄露或数据被破坏。例如输入1 OR 1 = 1
,会返回所有用户数据。 - 可读性差:硬编码的SQL语句嵌入Java代码中,使代码逻辑混杂,降低了代码的可读性,不利于团队开发和代码审查。
优化策略
- 使用参数化查询:使用JDBC的
PreparedStatement
代替直接拼接SQL。
public User getUserById(int id) {
String sql = "SELECT * FROM users WHERE id =?";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setInt(1, id);
// 执行查询并处理结果
} catch (SQLException e) {
// 异常处理
}
}
- 从外部配置文件加载SQL:将SQL语句放在配置文件(如
.properties
或.xml
)中,Java代码从配置文件读取SQL。 例如,在sql.properties
文件中:
getUserById=SELECT * FROM users WHERE id =?
在Java代码中读取:
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class UserDAO {
private static final Properties sqlProps = new Properties();
static {
try (InputStream inputStream = UserDAO.class.getClassLoader().getResourceAsStream("sql.properties")) {
sqlProps.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public User getUserById(int id) {
String sql = sqlProps.getProperty("getUserById");
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setInt(1, id);
// 执行查询并处理结果
} catch (SQLException e) {
// 异常处理
}
}
}
- 使用ORM框架:如Hibernate、MyBatis等。以MyBatis为例,通过XML映射文件配置SQL语句。
在
UserMapper.xml
中:
<mapper namespace="com.example.UserDAO">
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
在Java代码中调用:
public User getUserById(int id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
return sqlSession.selectOne("com.example.UserDAO.getUserById", id);
} finally {
sqlSession.close();
}
}
这样将SQL与Java代码分离,提高了代码的可维护性和安全性,同时利用框架的特性简化了数据库操作。