I'd like to be able to update a previously persisted object for which I have an id without having to retrieve it first. The main thing that I'm trying to avoid is having to copy multiple values into the object's fields when that object has been retrieved from the database. I have these values in a map with keys corresponding to the field names so it's trivial to create the object via a constructor with the map as an argument. Unfortunately, an object created this way results in a new database record when saved even though the id field is set to that of an existing record.
I'm currently using a slight variation on one of the examples shown here for copying Groovy class properties but it's not a very elegant solution for multiple reasons.
Basically I'd like to be able to do something like this:
class Foo {
int a
String b
}
def data = [id: 99, a: 11, b: "bar"] //99 is the id of an existing record
def foo = new Foo(data)
foo.update() //or some other comparable persistence mechanism
Thanks
As long as your map keys have the same name as your object properties, you can use executeUpdate without specifying the individual property names with a closure or function like the following:
def updateString = { obj, map ->
def str = ""
map.each { key, value ->
str += "${obj}.${key}=:${key},"
}
return str[0..-2]
}
def data= [foo:"bar", machoMan:"RandySavage"]
In this case, println updateString("f", data) returns "f.foo=:foo,f.machoMan=:machoMan".
Then you can do this:
Foo.executeUpdate("update Foo f set ${updateString("f", data)}", data)
Or of course you could combine that all together into one closure or function.
You can use the executeUpdate method on the GORM domain class:
Foo.executeUpdate("update Foo f set f.a=:a, f.b=:b where f.id=:id", data)
Related
For example:
class DogOwners(object): def get_this_animal(id, dog_name): return Dog(id=id, name=dog_name)
Would this return a new object or the existing one associated to the *args of get_this_animal()?
It returns the data I want but I can't tell if now I have two dogs with the same data
Any time you run Dog(...), you're creating a new object (assuming you didn't do anything special to the class to change that fact). Calling a class as a function constructs a new instance by default. You can also check this yourself using id:
# I added the necessary 'self' parameter, and changed the 'id' parameter
def get_this_animal(self, dog_id, dog_name):
new_dog = Dog(id=dog_id, name=dog_name)
print(id(self), id(new_dog)) # These will not be the same
return new_dog
That print will print two sepeate addresses/IDs, indicating that they're distinct objects. The dog_id and dog_name objects will be the same, however.
It would return a new dog with those attributes assuming that Dog(id=id, name =dog_name) is your constructor. The program doesn't have a way to instead return existing dogs with the same attributes as you've written it. If you wanted to not create a new dog then you'd need to store the data of all the dogs and search for that specific data to ensure you return the same dog. This storage and search can be done through several ways like a dictionary, array/list, and so on (likely a dictionary is better for what you're trying to do).
Suppose I have two tables USER_GROUP and USER_GROUP_DATASOURCE. I have a classic relation where one userGroup can have multiple dataSources and one DataSource simply is a String.
Due to some reasons, I have a custom RecordMapper creating a Java UserGroup POJO. (Mainly compatibility with the other code in the codebase, always being explicit on whats happening). This mapper sometimes creates simply POJOs containing data only from the USER_GROUP table, sometimes also the left joined dataSources.
Currently, I am trying to write the Multiset query along with the custom record mapper. My query thus far looks like this:
List<UserGroup> = ctx
.select(
asterisk(),
multiset(select(USER_GROUP_DATASOURCE.DATASOURCE_ID)
.from(USER_GROUP_DATASOURCE)
.where(USER_GROUP.ID.eq(USER_GROUP_DATASOURCE.USER_GROUP_ID))
).as("datasources").convertFrom(r -> r.map(Record1::value1))
)
.from(USER_GROUP)
.where(condition)
.fetch(new UserGroupMapper()))
Now my question is: How to create the UserGroupMapper? I am stuck right here:
public class UserGroupMapper implements RecordMapper<Record, UserGroup> {
#Override
public UserGroup map(Record rec) {
UserGroup grp = new UserGroup(rec.getValue(USER_GROUP.ID),
rec.getValue(USER_GROUP.NAME),
rec.getValue(USER_GROUP.DESCRIPTION)
javaParseTags(USER_GROUP.TAGS)
);
// Convention: if we have an additional field "datasources", we assume it to be a list of dataSources to be filled in
if (rec.indexOf("datasources") >= 0) {
// How to make `rec.getValue` return my List<String>????
List<String> dataSources = ?????
grp.dataSources.addAll(dataSources);
}
}
My guess is to have something like List<String> dataSources = rec.getValue(..) where I pass in a Field<List<String>> but I have no clue how I could create such Field<List<String>> with something like DSL.field().
How to get a type safe reference to your field from your RecordMapper
There are mostly two ways to do this:
Keep a reference to your multiset() field definition somewhere, and reuse that. Keep in mind that every jOOQ query is a dynamic SQL query, so you can use this feature of jOOQ to assign arbitrary query fragments to local variables (or return them from methods), in order to improve code reuse
You can just raw type cast the value, and not care about type safety. It's always an option, evne if not the cleanest one.
How to improve your query
Unless you're re-using that RecordMapper several times for different types of queries, why not do use Java's type inference instead? The main reason why you're not getting type information in your output is because of your asterisk() usage. But what if you did this instead:
List<UserGroup> = ctx
.select(
USER_GROUP, // Instead of asterisk()
multiset(
select(USER_GROUP_DATASOURCE.DATASOURCE_ID)
.from(USER_GROUP_DATASOURCE)
.where(USER_GROUP.ID.eq(USER_GROUP_DATASOURCE.USER_GROUP_ID))
).as("datasources").convertFrom(r -> r.map(Record1::value1))
)
.from(USER_GROUP)
.where(condition)
.fetch(r -> {
UserGroupRecord ug = r.value1();
List<String> list = r.value2(); // Type information available now
// ...
})
There are other ways than the above, which is using jOOQ 3.17+'s support for Table as SelectField. E.g. in jOOQ 3.16+, you can use row(USER_GROUP.fields()).
The important part is that you avoid the asterisk() expression, which removes type safety. You could even convert the USER_GROUP to your UserGroup type using USER_GROUP.convertFrom(r -> ...) when you project it:
List<UserGroup> = ctx
.select(
USER_GROUP.convertFrom(r -> ...),
// ...
I have a hash map which acts as store of objects - where key is class name & value is object
store = {} of String => Type
store["Animal"]= Animal.new
store["Book"]= Book.new
store["Car"]= Car.new
Here class is unknown to me, which means i can't use union type. Please tell me how to solve this problem ?
I am trying to create a wrapper around hash map for storage of objects, which will save object & return object by key.
Update 1
Classes will be passed as a parameter - like this
add_in_store(Animal)
add_in_store(Person)
Update 2
Crystal play link of what i am trying to do - https://play.crystal-lang.org/#/r/8lwx
Solution of problem doesn't have to be in same way as what i m doing. It can be with any other approach like using proc or macro etc.
Hash is a generic type, to wrap it in a way where you cannot or don't want to restrict the possible types of the generic arguments, you should make your wrapper itself generic.
class Store(V)
#store = {} of String => V
def add(name, value : V)
#store[name] = value
end
end
Then the consumer of your wrapper has to give the possible types.
I'm new to JOOQ and currently fail to map a joined query to Map<K, List<V>>: the list always only contains one element.
Here's my code:
DSL.using(...)
.select(ORDER.fields())
.select(ORDER_ITEM_ARTICLE.fields())
.from(ORDER)
.leftOuterJoin(ORDER_ITEM_ARTICLE).on(ORDER.ID.eq(ORDER_ITEM_ARTICLE.ORDER_ID))
// to Map<InOutOrder, List<OrderItemArticle>>
.fetchGroups(
r -> r.into(ORDER).into(InOutOrder.class),
r -> r.into(ORDER_ITEM_ARTICLE).into(OrderItemArticle.class)
)
// map to InOutOrder
.entrySet().stream().map( e -> {
// e.getValue() always returns list with only 1 element?!
e.getKey().articles = e.getValue();
return e.getKey();
})
.collect(Collectors.toList())
;
Say I have 1 row in ORDER and 2 corresponding rows in ORDER_ITEM_ARTICLE. Running the SQL returned by .getSQL() (after .fetchGroups()), returns me 2 rows as expected, so I assumed the fetchGroups() call will populate my list with two entries as well?!
What am I missing?
Thanks!
Update:
As requested, the InOutOrder class:
public class InOutOrder extends Order {
public List<OrderItemArticle> articles;
public List<OrderItemOther> others;
public List<OrderItemCost> costs;
public List<OrderContact> contacts;
public List<EmailJob> emailJobs;
}
So this is just an extension of the JOOQ POJO class and is used for JSON communication with the API clients...
fetchGroups() simply puts objects in a LinkedHashMap. You have to adhere to the usual Map contract, which means implementing equals() and hashCode(). Without it, each object you're creating (or which jOOQ is creating for you) will use identity comparison, so you get every "value" only once in the result.
I dont know how to describe the problem, so weird. I have function like this:
long getPersonId(...){
//...
}
The above function returns Id of a person based on some arguments.
So I logged the return value of the function and it is 1.
Then I have code like this:
person = myMap.get(getPersonId(..))
which returns null object but this returns a valid Person object, why?:
person = myMap.get(1)
But as I described before getPersonId(..) returns 1, which basically means
myMap.get(getPersonId(..)) == myMap.get(1)
myMap is typed as Map<Long, Person> myMap
What is happening here?
In Groovy, as in Java, 1 is an int literal, not a long, so
myMap.get(1)
is attempting to look up the key Integer.valueOf(1), whereas
myMap.get(getPersonId(..))
is looking up the key Long.valueOf(getPersonId(...)). You need to make sure that when you populate the map you are definitely using Long keys rather than Integer ones, e.g.
myMap.put(1L, somePerson)
In your original version of this question you were calling the GORM get method on a domain class rather than the java.util.Map.get method, and that should work as required as the GORM method call converts the ID to the appropriate type for you before passing it on to Hibernate.
I am so sorry the problem was when I initialize the map myMap
Map<Long, Person> myMap = [1, new Person()]
when you say something like this the key is an integerbut not a long still groovy not complaining.
So the problem is my method was returning a long value (1L) but my actual key on the map is integer value(1).
So changing my map init to Map<Long, Person> myMap = [1L, new Person()] solved the problem.
Probably this due to dynamic nature groovy but irritating unless you know how dynamic langs behave lol.