我想将User
个实例保存到 H2 数据库。
为了将新用户保存到DB,我遇到以下异常:
Caused by: org.h2.jdbc.JdbcSQLException: Column count does not match; SQL statement:
INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets)
VALUES (?, ?, ?, ?, ?, ) [21002-191]
这是DAO片段:
@Override
public Integer create(User entity) {
String sql = "INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) " +
"VALUES (:id, :name, :birthday, :email, :role, :tickets)";
SqlParameterSource parameterSource =
new MapSqlParameterSource("id", entity.getId())
.addValue("name", entity.getName())
.addValue("birthday", entity.getBirthday())
.addValue("email", entity.getEmail())
.addValue("role", entity.getRole())
.addValue("tickets", entity.getBookedTickets());
Logger.info("Create user: " + entity);
return getNamedParameterJdbcTemplate().update(sql, parameterSource); <== It fails here
}
用于创建数据库的SQL脚本如下所示:
----------------------
-- Create Users table
----------------------
CREATE TABLE Users (
user_id INTEGER PRIMARY KEY NOT NULL,
user_name VARCHAR(30) NULL,
user_birthday DATETIME NULL,
user_email VARCHAR(30) NULL,
user_role VARCHAR(20) NULL,
user_tickets VARCHAR(100) NULL,
);
-----------------------
-- Create Tickets table
-----------------------
CREATE TABLE Tickets (
tick_id INTEGER PRIMARY KEY NOT NULL,
event_id VARCHAR(30),
tick_price DECIMAL(8,2),
user_id INTEGER,
);
以下是User POJO
:
public class User {
private Integer id;
private String name;
private Calendar birthday;
private String email;
private String role;
private Set<Ticket> bookedTickets = new HashSet<>();
// getters / setters
我认为它无法写入Set<Ticket>
,但我不知道如何解决此问题。
更新:
为了执行数据库访问,我正在使用 - Spring JDBC
。
完全NamedParameterJdbcTemplate
:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean class="net.lelyak.edu.dao.NamedParameterJdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
public class NamedParameterJdbcDaoImpl extends NamedParameterJdbcDaoSupport {
@Autowired
private DataSource dataSource;
@PostConstruct
private void initialize() {
setDataSource(dataSource);
}
}
DAO实施:
@Repository
public class UserDaoImpl extends NamedParameterJdbcDaoImpl implements IGenericDao<User, Integer> {
@Override
public Integer create(User entity) {
// todo move SQL queries to utility class
String sql = "INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) " +
"VALUES (:id, :name, :birthday, :email, :role, :tickets)";
// see create() at above text
有任何建议吗?
答案 0 :(得分:1)
列user_tickets
为VARCHAR(100)
,但您分配给:tickets
的值为Set<Ticket>
,那该怎么办呢?
Spring并不知道你在做什么,但它假设你在使用多值参数时构建IN
子句,例如: x IN (:tickets)
,因此它会用适当数量的参数标记替换:tickets
。例如。如果你的集合有3个值,它将变为x IN (?,?,?)
。
您的Set
为空,因此不会生成任何标记。从技术上讲,我认为它应该抛出异常,因为即使对于IN
条款,这也不会有效,但它没有。
那么,如果您的user_tickets
有值,那么您期望列Set<Ticket>
的价值是多少? Set
的字符串版本,例如[Ticket1, Ticket2]
?如果是,请拨打toString()
。
.addValue("tickets", entity.getBookedTickets().toString());
然后交叉你的手指,希望不会超过100个字符。
答案 1 :(得分:1)
抱歉,我忘记了。 我的假设是用户可以有很多票,一张票只属于一个用户。您无法在数据库单元中保存所有集合,因此解决方案是更改关系并在票证上保存用户ID。贝娄就是全部。我创建了服务类,检查你是否有数据库用户,如果没有 - 保存它。
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private Calendar birthday;
private String email;
private String role;
@Transient
private Set<Ticket> bookedTickets = new HashSet<>(); //I cant save collection into database
//getters and setters
}
@Entity
public class Ticket {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String desc;
private int number;
/** id of customer as owner this ticket */
@ManyToOne
private User user;
//getters and setters
}
用于保存的userDAO方法中的:
public void save(User user){
String sql = "INSERT INTO User ( name, birthday, email, role) VALUES (:name, :birthday, :email, :role)";
SqlParameterSource parameterSource =
new MapSqlParameterSource("name", user.getName())
.addValue("birthday", user.getBirthday())
.addValue("email", user.getEmail())
.addValue("role", user.getRole());
namedParameterJdbcTemplate.update(sql, parameterSource);
sql="SELECT id FROM User WHERE name = :name AND birthday=:birthday AND email=:email AND role=:role";
Integer id = namedParameterJdbcTemplate.query(sql, parameterSource, new ResultSetExtractor<Integer>() {
@Override
public Integer extractData(ResultSet result) throws SQLException,DataAccessException {
return result.getInt("id");
}
});
user.setId(id);
}
用于保存的ticketDAO方法中的:
public void save(Ticket ticket){
String sql = "INSERT INTO Ticket (desc , number, user_id) VALUES (:desc, :number, :userId)";
SqlParameterSource parameterSource =
new MapSqlParameterSource("desc", ticket.getDesc())
.addValue("number", ticket.getNumber())
.addValue("userId", ticket.getUser().getId());
namedParameterJdbcTemplate.update(sql, parameterSource);
}
和saveTickets的服务:
public class UserService {
private TicketDAO ticketDAO;
private UserDAO userDAO;
public void saveTicketsForUser(User user){
if(user.getId()==null){
//if user is not saved in database
userDAO.save(user);
}else{
//if you have this client in database, you don't need to save client
}
for(Ticket ticket: user.getBookedTickets()){
ticket.setUser(user);
ticketDAO.save(ticket);
}
}
}
你可以使用xml将dao类注入服务。
答案 2 :(得分:0)
解决方案是重新设计逻辑一点点。将主要部分保存逻辑移动到父抽象类:
public abstract class BaseDAO<ENTITY extends BaseEntity> extends NamedParameterJdbcDaoSupport implements IGenericDao<ENTITY> {
private final String tableName;
private final Class<ENTITY> entityClass;
private final List<String> fields;
private final String insertSQL;
private final String updateSQL;
public BaseDAO(Class<ENTITY> entityClass, String tableName, List<String> fields) {
this.entityClass = entityClass;
this.tableName = tableName;
this.fields = fields;
// init SQLs
StringBuilder sbInsertSQL = new StringBuilder();
StringBuilder sbUpdateSQL = new StringBuilder();
sbInsertSQL.append("INSERT INTO ").append(tableName).append(" (");
sbUpdateSQL.append("UPDATE ").append(tableName).append(" SET ");
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
sbInsertSQL.append(", ");
sbUpdateSQL.append(", ");
}
sbInsertSQL.append(fields.get(i));
sbUpdateSQL.append(fields.get(i)).append("=:").append(fields.get(i));
}
sbInsertSQL.append(") ").append("VALUES (");
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
sbInsertSQL.append(",");
}
sbInsertSQL.append(":").append(fields.get(i));
}
sbInsertSQL.append(")\n");
sbUpdateSQL.append(" WHERE id=:id\n");
this.insertSQL = sbInsertSQL.toString();
this.updateSQL = sbUpdateSQL.toString();
Logger.debug("BaseDAO(), insertSQL: [" + insertSQL + "]");
Logger.debug("BaseDAO(), updateSQL: [" + updateSQL + "]");
}
@Override
public Long save(ENTITY entity) {
long res;
if (entity.getId() == null) {
res = insert(entity);
} else {
update(entity);
res = entity.getId();
}
return res;
}
Child DAO将如下所示:
public class UserDAO extends BaseDAO<User> {
private static final String USER_TABLE_NAME = "t_user";
private static final String userFields[] = {"name", "birthday", "email", "password", "role", "enabled"};
public UserDAO() {
super(User.class, USER_TABLE_NAME, Arrays.asList(userFields));
}
另外,使用自动递增id的用户表:
----------------------
-- create t_user table
----------------------
CREATE TABLE t_user (
id INT GENERATED ALWAYS AS IDENTITY CONSTRAINT pk_user PRIMARY KEY,
name VARCHAR(60) NOT NULL,
birthday DATE,
email VARCHAR(60),
password VARCHAR(100),
role VARCHAR(300),
enabled SMALLINT(6)
);
此外,应使用父逻辑更新模型:
public abstract class BaseEntity {
protected Long id = null;
protected String name;
public class User extends BaseEntity {
private Date birthday;
private String email;
private String password;
private String role;
private boolean enabled;